2

tl;dr

Is it possible to include an external library (and create instances of objects represented within that library) based on some condition? This must be done at compile-time, so separate builds?

Background information

Warning: Wall of text.

If I have some code that requires a link to an external class:

using myExternalLibrary;
namespace myNamespace
{
  public class Engine
  {
     public Engine()
     {
       // external class is contained
       // within myExternalLibrary
       public externalClass instanceOfExternalClass
              = new externalClass();
     }
  }
}

// external class
namespace myExternalLibrary
{
  public class externalClass
  {
     public externalClass()
     {
       // constructor logic
     }
  }
}

By providing references to myExternalLibrary in the project that contains myClass and providing a using statement in a relevant place, I can create an instance of externalClass as and when I need it within myClass. This I am fine with. If I have some very common code that requires several different external libraries, I can do the same thing but with each of the external libraries that are required (i.e add a reference and a using statement for each one of the external libraries).

However, due to the design of the software I am working on, I am required to use an external library only when certain conditions are met. I am also not to include references to those external libraries if they are not required.

Class Detail

I can't go into specifics about the class design, but this is basically what I have: I have a class (this is the Engine class) that is required to act as a bridge between the GUI and some hardware. The Engine class requires external libraries to allow it to communicate with external hardware.

I have two external libraries for two separate types of hardware:

  • InternallyDevelopedHardwareControlLibrary (our internally developed hardware library for controlling our internally developed hardware)
  • LicensedHardwareControlLibrary (a library that we have licensed for controlling some hardware we have bought in).

By default, the Engine will communicate via InternallyDevelopedHardwareControlLibrary to our internally developed hardware. However, Engine also needs to be able to communicate via LicensedHardwareControlLibrary to the licensed hardware, if (and only if) the end-user has licensed our software to run along side the externally developed hardware.

LicensedHardwareControlLibrary will be licensed to the end-user with our software to some of our end-users. In this instance, LicensedHardwareControlLibrary is required to be shipped with our software. However, if the end-user has is not licensed to use LicensedHardwareControlLibrary, we are not permitted to ship that library with out software.

Since Engine is taking the place of communicating with the external hardware via InternallyDevelopedHardwareControlLibrary by default, it requires a reference to it. However if a user is licensed to utilise LicensedHardwareControlLibrary, I need to add a reference to that, but only if the user is licensed to use that library.

Question

Since Engine is required to be generic, I need to figure out a way of conditionally including this second library.

My initial thought was to move all of the elements of Engine to an abstract class, and to have two separate Engines (one for each of the libraries) that extend the abstract Engine class. Something like:

public class Engine
{
  // all common code for each Engine
  // type in here
}

public class StandardEngine : Engine
{
  // the engine that will utilise
  // InternallyDevelopedHardwareControlLibrary
}

public class LicensedEngine : Engine
{
  // the engine that will utilise
  // LicensedHardwareControlLibrary
}

The StandardEngine code will make use of an instance of the objects represented in InternallyDevelopedHardwareControlLibrary, and LicensedEngine will make use of an instance of the objects represented in LicensedHardwareControlLibrary.

Or more simply put: Each of the less-abstract versions of Engine require to operate on a representation of an object that is contained within either InternallyDevelopedHardwareControlLibrary (StandardEngine) or LicensedHardwareControlLibrary (LicensedEngine).

The standard build will only contain the StandardEngine class, however for some users we will need to run a separate build which also contains the LicensedEngine class.

However, I feel that this will not be optimal because LicensedEngine will need to be external from the main project/library because it cannot be shipped in our standard build. What are the ways in which I can achieve this?

1
  • 1
    I think what should work is to put both engines into the same assembly that does reference LicensedHardwareControlLibrary. For those without the special license, simply don't include the actual library assembly into the download (or whatever). If the code never uses LicensedEngine, it will never attempt to load LicensedHardwareControlLibrary and everything will work fine.
    – svick
    Commented Jan 7, 2014 at 13:58

1 Answer 1

3

Make a big decision first: compile time or runtime?

If you do want to use assembly references, they are compile-time only. Then you can use #if directives in your code and you can use MSBuild tricks (edit the csproj file outside Visual Studio) to conditionally have the assembly reference. MSBuild is a very powerful tool, and if you are careful, it won't mess too much with Visual Studio. Or you can just have multiple csproj files, living in the same directory, using (almost) the same source files, and different references. This all happens at compile time.

A completely different approach is doing the reference loading during runtime, that's why they invented reflection. You are lucky if the external reference implements a common interface because once you get the type with reflection, you can cast it to your interface and use it normally. Otherwise, you are stuck with Invoke()-ing MethodInfo-s. Or, if your requirements allow it, you can create a very small adapter layer assembly between the external library and yours, as a 3rd assembly. If you need to use the external library, you load your adapter assembly dynamically, cast it to an interface, and use the external library through that layer, no Invoke() anywhere.

5
  • We're going with compile time assembly references. Please ignore the "at run time" in the title, as it was a working title that I forgot to alter after I had written the question. Commented Jan 7, 2014 at 12:15
  • 1
    OK. You can google preprocessor directives, you can drive them directly with conditional compilation symbols from Visual Studio, or MSBuild logic. Outside Visual Studio, you can put conditions on mostly anything in a .csproj file, including <Reference> tags AFAIK. If you do that and later edit the project in Visual Studio, that could be problematic though, but generally, Visual Studio can handle these things just fine.
    – fejesjoco
    Commented Jan 7, 2014 at 12:19
  • Excellent, thank you. If we had gone down the runtime route, we'd have to include the licensed library with our build right? Otherwise we'd get exceptions thrown at runtime if we tried to Invoke something that cannot be found, right? Commented Jan 7, 2014 at 12:21
  • 1
    You don't have to include it ever. If you load the library with reflection, then naturally there is no strong dependency, so you don't load it if it's not there. If there is an assembly reference, then I'm pretty sure you'll have no problems until you step into a method that does require that assembly. But of course it's a lot safer to include it after you compiled it with a strong reference.
    – fejesjoco
    Commented Jan 7, 2014 at 12:23
  • @JamieTaylor - As per svick's comment above, if you don't include it, you still won't get exceptions at runtime if you never try to call it.
    – Bobson
    Commented Jan 7, 2014 at 18:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.