How to enable forward compatibility on a reusable .NET library?
Asked Answered
E

2

6

I'm in the process of creating a new minor release of a toy project of mine. This project is released on NuGet and is compatible with .NET 4.0 and up. Some of the new features I'm introducing require .NET 4.5 (users should be able to resolve IReadOnlyCollection<T> and IReadOnlyList<T>, both interfaces that were introduced in .NET 4.5), but I need to keep the project compatible with .NET 4.0, since not all developers can easily migrate to the latest .NET framework.

So the problem I’m facing is how to solve this ‘forward-compatibility’ problem. There are two solutions I’ve thought about, but both are not very attractive, so hopefully anybody can give me some ideas or guidance here.

Here are the two solutions I came up with:

Solution 1: Use #if compiler directives and build a DLL per .NET framework version and ship those versions using the NuGet packages and download at the project site.

Downside of this method is that when developers update their Visual Studio project from .NET 4.0 to .NET 4.5, they don't automatically get the .NET 4.5 version (with .NET 4.5 specific features). This violates the Principle of least astonishment and would leave developers dazed why the feature is not working, when they try using it a few months later.

Solution 2: Use one single DLL and emit type's on the fly that implement both new interfaces when they exist in the current app domain. This allows shipping a single DLL to the user and allows features to come available when the developer switches .NET framework versions in their project. This will make things 'just work'. This is the direction I’m currently heading btw.

Since I need to return a type that needs to implement the interfaces, the downside is that that type must be created at runtime using Reflection.Emit, ModuleBuilder, TypeBuilder, and the like. This is seriously nasty shizzle. But besides that, since this type must be created in a new (anonymous) assembly, I must make some internal types public (a type it needs to inherit from and an interface it needs to implement). Making those internal types public pollutes the API of the project and will disallow me from making changes to those types.

I believe these are my options, but I might be missing something obvious. So my question is, am I missing a possibility? Is there a way to circumvent the problems for solution 1 or would it be better to go with the hardcore root of runtime type emitting?

Electuary answered 1/8, 2013 at 14:43 Comment(7)
json.net uses solution 1.Mountebank
I don't think the first option flies in the face of the principle: why would I expect my third party references to be raped like that? I'd be astonished if they did!Metsky
Not sure if it really fits here or if it is even related here but does assembly redirection strike some chord?Kamila
@Sandeep: I'm not sure. Can you explain (in an answer if you will)?Electuary
When dealing with new features that cannot be implement in previous versions of .NET Framework its important NOT to be fancy. The simple solution is to have two versions of the library itself. One will be compiled to support previous supported versions the other will support higher supporter versions. While I suppose you can use a solution like Dynamitey the question you have to ask yourself, is the added complexity actually worth it, is it really hard to release two different 1.4.0 library files? The users of your library should understand how to reference the correct version of the library.Trexler
@DanielA.White: What functionality does Json.NET add in its .NET 4.5 release? Ninject seems to have a 4.0 and 4.5 lib, but both dlls seem equivalent.Electuary
@Electuary win8 support and likely asyncMountebank
B
2

Have you thought about another custom assembly with the missing items in it? Then you test if a type/method exists (that would only exist in .net 4.5) and if it does, you load the assembly in.

That way you can keep the exact same methods and classes, and save yourself pain of doing all of that crazy emit (not to mention the perf hit you will take if you find yourself doing that much).

Birthwort answered 1/8, 2013 at 14:55 Comment(4)
Well, that's actually a really good idea. The only downside of this is having an extra assembly, but I might even place that assembly in a resource (evil grin).Electuary
I think in a resource might be a safer place to put it too.Bumbling
@jbtule, Although loading the assembly in a resource would nice, it is extremely nasty to do this, while using signed assemblies, because there is a dependency cycle between the assemblies. Core must be built before this new assembly (because it depends on core), but core needs to store that assembly in its resource. You see the problem?Electuary
True, but adding resources is one of Mono.Cecil's straight forward manipulations.Bumbling
B
1

I have a project called Dynamitey that allows you to load a type at runtime and called it's static methods and constructors with the DLR. Which would be much less messy than a lot of reflection or emitting code to load an api that is not necessarily available.

dynamic bigIntType = new DynamicObjects.LateType("System.Numerics.BigInteger, System.Numerics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");

if (bigIntType.IsAvailable)
{

  var one = bigIntType.@new(1);
  var two = bigIntType.@new(2);

  Assert.IsFalse(one.IsEven);
  Assert.AreEqual(true, two.IsEven);

  var tParsed = bigIntType.Parse("4");

  Assert.AreEqual(true, tParsed.IsEven);
}

I also have a project called ImpromptuInterface, that will emit proxy types for interfaces around objects that duck callable match it (also uses DLR).

var targetType =Type.GetType("System.Collections.Generic.IReadOnlyList`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");

var list = new List<string>{"lala", "la","lala"};
object readonlyList;
if(targetType != null){
    readonlyList = Impromptu.DynamicActLike(list, targetType);
}
Bumbling answered 1/8, 2013 at 15:11 Comment(2)
Interesting tool, but for my project, I don't want to depend drag any external libraries in.Electuary
Yeah I think loading an assembly from an embedded resource is a better solution in your case.Bumbling

© 2022 - 2024 — McMap. All rights reserved.