2

In classic C, I may have a library at version 1.0, which defines a constant in its .h file like:

 #define LIBRARY_API_VERSION_1_0

And I can do things like this in my application code:

#include "LibraryApi.h"
// ...
int success; 
#ifdef LIBRARY_API_VERSION_1_0
    int param = 42;
    success = UseThisMethodSignature(42);
#endif
#ifdef LIBRARY_API_VERSION_2_0
    float param = 42.0f;
    success = UseOtherMethodSignature(param);
#endif

Now I'm working in C#. So, apparently #defines are only scoped to the file they're defined in, so I looked into the solution described here of using a static class with constants. But, that solution requires the checking to happen at runtime, which introduces a number of problems:

  • Potentially inefficient, if I'm running over the same code over and over again checking an extra conditional (though if it's a const, perhaps the compiler or .NET runtime is smart enough to avoid this?)
  • You can't do things that would throw compiler errors. In my above example, I've defined param twice with two different types. Also UseOtherMethodSignature may not exist as a function, which will not compile if both blocks are there only separated by if/else.

So, what is the accepted solution for this type of problem? My scenario is that I have multiple versions of a web service API (with varying degrees of differences depending on what you're doing with it) and I want to be able to compile against either without commenting/uncommenting a bunch of code or some other equally silly manual process.

Edit

For what it's worth, I'd prefer a compile-time solution--in my scenario I know when I compile which version I'm going to use, I don't need to figure out which version of the library is available on the system at runtime. Yes, that will work, but seems like overkill.

Community
  • 1
  • 1
S'pht'Kr
  • 2,809
  • 1
  • 24
  • 43
  • What would you expect to happen if you built using v1 of the API, but only v2 was present at execution time? Bear in mind that although `#define` itself only works at a source file level, you can specify the same tokens as project properties. – Jon Skeet Mar 31 '14 at 13:03
  • @JonSkeet Good question, I suppose it would crash. I think this is precisely what happens when, for instance, you build a binary from C code and _dynamically_ link it but run it on a system without that library/version. But in my scenario, this is a library that I have the source for, and I compile it into a deployable ASP.NET app, so it's not so much a concern. It's more like static linking in this case. – S'pht'Kr Mar 31 '14 at 13:08

3 Answers3

1

I would aim to abstract this into different wrapper libraries. They would be separate projects in Visual Studio and reference different versions of your framework.

// Shazaam contract.
public interface IShazaamInvoker {
    Boolean Shazaam();
}

// ShazaamWrapper.v1.dll implementation
public class ShazaamInvoker : IShazaamInvoker {
    public void Shazaam() {
        Int32 param = 42;
        return UseThisMethodSignature(param);
    }
}

// ShazaamWrapper.v2.dll implementation
public class ShazaamInvoker : IShazaamInvoker {
    public void Shazaam() {
        Single param = 42f;
        return UseOtherMethodSignature(param);
    }
}

// Determine, at runtime, which wrapper to use.
var invoker = (IShazaamInvoker)(/*HereBeMagicResolving*/)
invoker.Shazaam();
sisve
  • 19,501
  • 3
  • 53
  • 95
  • Interesting, this is probably a good solution, but this results in both versions being compiled in and I have to decide which version I'm using at runtime, correct? There's not a way to do this all at compile time? – S'pht'Kr Mar 31 '14 at 13:16
  • Exactly, which means that your users can change framework versions without requiring a recompiled version of your application. You'll just load another version of the wrapper and everything will work again. This will also make it a lot easier to unit test, you can mock IShazaamInvoker and avoid calling your framework at all in your tests. – sisve Mar 31 '14 at 14:17
0

I suggest using a DI framework to load the appropriate class / dll. If you can refactor your code to use interfaces then you can create an abstraction layer across different versions. See this link as to the different frameworks available.

Perhaps another solution in keeping with the compile time nature of your question is to use generated code with T4

Community
  • 1
  • 1
auburg
  • 1,373
  • 2
  • 12
  • 22
  • Hmm…okay, I've seen DI before but never really understood what it's for--maybe this is it. Building an abstract parent class/interface occurred to me, but this is a whole API that is generated with `wsdl.exe` and comes out to 250,000 lines of code. I don't think it will be practical for me to do the necessary amount of refactoring :-) – S'pht'Kr Mar 31 '14 at 13:11
0

You must define a compilation symbol at the project level. You do that in the project properties. These symbols can be referenced with the #if directive.

You could also create a project build configuration that includes one or the other compilation symbol and also check the configuration in the project file to include one or the other .dll reference based on the symbol so that you can properly build and debug both versions just by choosing the version from the dropdown in the toolbar.

Knaģis
  • 20,827
  • 7
  • 66
  • 80