Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 844
Steven Avatar asked Aug 01 '13 14:08

Steven


People also ask

Is .NET forward compatible?

NET frameworks are mostly backward compatible. We also know that forward compatibility is only supported for framework 1.1, and not beyond that. However, the CLR version for frameworks 2.0, 3.0, and 3.5 is the same: CLR 2.0.

Is. net 4. 5 backwards compatible with 3. 5?

NET Framework 4.5 is backward-compatible with applications that were built with the . NET Framework versions 1.1, 2.0, 3.0, 3.5, and 4.

Is C# backwards compatible?

C# 4.0 is nearly backwards compatible with previous versions but there are a few breaking changes. For most ordinary code you won't notice these breaking changes. For the new features in C# 4 you can check out the Wikipedia article which has quite a good summary with examples.


2 Answers

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).

like image 97
ferventcoder Avatar answered Sep 18 '22 18:09

ferventcoder


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);
}
like image 28
jbtule Avatar answered Sep 22 '22 18:09

jbtule