Can I have my assembly reference any version of another assembly? [duplicate]
Asked Answered
P

2

15
  • I am developing a class library (MyClassLibrary).
  • I depend on a third party class library (ThirdPartyClassLibrary).
  • I need to use the same version of ThirdPartyClassLibrary as my users. e.g., if I set a static value in ThirdPartyClassLibrary the user needs to see that change.
  • Users of my class may be depending on any one of 4 different versions of ThirdPartyClassLibrary.
  • ThirdPartyClassLibrary is large, I do not want to distribute it with my software.
  • I have reflected on all 4 versions of ThirdPartyClassLibrary and validated that the things I will be doing with them are compatible across all versions (interfaces are the same, methods signatures are the same, etc.).
  • I need calls into ThirdPartyClassLibrary to be performant! I can't reflect on everything every time I need to call something.
  • MyClassLibrary will be loaded at runtime, so I can't expect users to mess with assembly binding redirects or other develop-time settings (or any settings at all, my users are resistant to doing anything).
  • I would like to benefit from compile-time checking of my code, so ideally no reflection at all.

How can I write MyClassLibrary such that when it is loaded into the process everything works correctly with whichever version of ThirdPartyClassLibrary the user has loaded?

Pelion answered 4/6, 2014 at 21:56 Comment(7)
Use <bindingRedirect> in your .config file to force the CLR to ignore the version number.Mixup
The .config file for my assembly? Would I just need to make sure my .config file is sitting next to my assembly and the CLR will do the rest?Pelion
No, your EXE, like yourapp.exe.configMixup
I don't have an exe. I am distributing a class library only. My class library may be used by another class library or by an exe and I would like it to all "just work" for my users.Pelion
Does ThirdPartyClassLibrary have a strong name?Beekeeper
Yes, ThirdPartyClassLibrary has a strong name.Pelion
Possible duplicate of Is it possible to replace a reference to a strongly-named assembly with a "weak" reference?Blown
C
12

One workaround would be to use the AppDomain.AssemblyResolve event at runtime. This fires whenever the resolution of an assembly fails. You can use this to load a different version of an assembly to that which the CLR is trying to load.

I've added a very simple demo on GitHub here:

https://github.com/danmalcolm/AssemblyResolutionDemo

This is set up as follows:

  • The main application App.exe directly references assembly ThirdPartyLibrary.dll version 2.0.0.0.

  • It also references MyLibrary, which references an older version of ThirdPartyLibrary version 1.0.0.0.

  • The AppDomain.AssemblyResolve event is used to redirect to the version used by the application when version 1.0.0.0 fails to load

AssemblyResolve is handled as follows:

public static void Initialise()
{
    AppDomain.CurrentDomain.AssemblyResolve += ResolveThirdPartyLibrary;
}

private static Assembly ResolveThirdPartyLibrary(object sender, ResolveEventArgs args)
{
    // Check that CLR is loading the version of ThirdPartyLibrary referenced by MyLibrary
    if (args.Name.Equals("ThirdPartyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=fbcbfac3e44fefed"))
    {
        try
        {
            // Load from application's base directory. Alternative logic might be needed if you need to 
            // load from GAC etc. However, note that calling certain overloads of Assembly.Load will result
            // in the AssemblyResolve event from firing recursively - see recommendations in
            // http://msdn.microsoft.com/en-us/library/ff527268.aspx for further info
            var assembly = Assembly.LoadFrom("ThirdPartyLibrary.dll");
            return assembly;
        }
        catch (Exception exception)
        {
            Console.WriteLine(exception);
        }
    }
    return null;
}

We need to bind to the event before ThirdPartyLibrary is loaded, hence the explicit Initialise method.

Note also that the event only fires when the resolution of an assembly fails. If the version of ThirdPartyLibrary referenced by MyClassLibrary (1.0.0.0) were available in the GAC, then it would be loaded successfully and AssemblyResolve wouldn't fire. There would then be 2 different versions in use.

I'm demonstrating here that this mechanism could be used, I'm not saying it's a good idea. There are several things you'd need to take into account based on the environment in which your app is running and how it is set-up / installed / maintained etc.

Candis answered 11/6, 2014 at 10:14 Comment(5)
My original answer aimed to demonstrate that you can't build an assembly in a way that will use any version of a referenced assembly. I've added a separate answer with a possible workaround.Candis
Exactly what I was looking for, thanks! I am aware of all of the caveats with loading mismatched versions of assemblies, unfortunately in my particular situation it is better than using reflection everywhere both for compile time checking and performance reasons. Because of that, I am willing to sink the additional development time costs into making sure that I account for all of the caveats.Pelion
This is really great for plugins located in own directories that reference other assemblies. Thanks!Roundup
@MicahZoltu , I'm running into the same problem as you do. Did the above solution from Dan help you solve the problem ? Or was there a simpler approach of changing the .config file worked too ? From reading this forum it appears there is not way to resolve this thru .config file change.Kalindi
I believe so, yes. Definitely be aware of all of the caveats though, I recommend allocating a solid month of engineering to addressing all of the ins and outs if you are going down this path for a production system. In our case at the time we really needed the functionality and was thus worth the effort.Pelion
C
8

No, you can't build MyClassLibrary with a reference to ThirdPartyClassLibrary in a way that says "just use whatever version of ThirdPartyClassLibrary.dll is available at runtime".

When you build your library, the version number of any referenced assemblies are included in the assembly manifest. Running the ILDASM tool against your assembly would show something like this:

...
.assembly extern ThirdPartyClassLibrary
{
  ...
  .ver 1:0:0:0
}
...

Both the name and version of ThirdPartyClassLibrary are specified. At runtime, the CLR will attempt to load ThirdPartyClassLibrary.dll when it first runs instructions in MyClassLibrary.dll that reference it. It will look specifically for version 1.0.0.0 of ThirdPartyClassLibrary.dll (and will also require a matching public key if it's a strong-named assembly).

Here's a quick overview of how the CLR locates and binds to assemblies at runtime (full details at http://msdn.microsoft.com/en-us/library/yx7xezcf(v=vs.110).aspx):

Step 1 - Determine the correct assembly version by examining configuration files - we'll return to this below, but for now, if you don't tell it otherwise, the CLR will attempt to load the exact version specified in the referencing assembly, so it will be looking for version 1.0.0.0.

Step 2 - Check whether the assembly name has been bound to before and, if so, uses the previously loaded assembly. Note that "assembly name" in this context includes the name and version, public key token etc, not just the name of the dll file.

Step 3 - Check the Global Assembly Cache GAC (strong-named assemblies only)

Step 4 - Locate the assembly through codebases or probing - essentially the CLR looks in different places to try to find (the specific version of) AssemblyB.dll somewhere. An error will occur if it can't find the specific version. It won't automatically fall back to an earlier or later version.

Unfortunately, this means that things won't "just work" and support what you describe above. If an application that references MyClassLibrary itself references a later version (2.0.0.0) of ThirdPartyClassLibrary, some bad things could happen when resolving MyClassLibrary's reference to ThirdPartyClassLibrary:

  • The CLR can't find version 1.0.0.0 of AssemblyB used by Assembly A and an error occurs
  • Version 1.0.0.0 happens to be installed in the GAC and is loaded successfully. While the code in the application is using ThirdPartyClassLibrary version 2.0.0.0, your library is using ThirdPartyClassLibrary version 1.0.0.0.

One thing that you can do is configure the application using your library so that the CLR will unify references to different versions of ThirdPartyClassLibrary.dll to a single version. This brings us back to step 1 of the assembly binding process outlined above - we essentially change the version of ThirdPartyClassLibrary that the CLR is looking for.

Binding redirects (http://msdn.microsoft.com/en-us/library/twy1dw1e.aspx) are designed to channel references to different versions of an assembly to a single version. These are usually defined within an application's configuration file (Web.config, MyApp.exe.config), but can also be defined globally at machine level (machine.config).

Here's an example of a binding redirect that redirects all earlier versions of ThirdPartyClassLibrary.dll to version 2.0.0.0:

<configuration>

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="AssemblyB" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

</configuration>

Note that this can be automatically handled by Visual Studio 2013, which detects cases where different versions of an assembly are referenced and adds the binding redirects for you. There's also an Add-BindingRedirect command available in the NuGet Package Manager Console.

Binding redirects are a possible solution and could be a pragmatic choice in some scenarios. However, they could also confuse users of your library. If practical, you should consider distributing different versions of your library, built against the different versions of the third party library.

Candis answered 7/6, 2014 at 19:1 Comment(7)
I am looking for assembly binding redirection, but I don't want my users to have to do it. As I mentioned, my assembly is dynamically loaded at runtime so Visual Studio doesn't have the opportunity to automatically update their app.config for them and they won't get any compile time errors/warnings alerting them of the need for assembly redirection. It will just fail at runtime which is a poor user experience. Ideally, I would like a way to programmatically setup binding redirection before ThirdPartyLibrary is loaded (like in my initialization code).Pelion
Understood. What's the setup with dynamic loading? Is it a plugin scenario where users configure their app with the name of your assembly and it gets loaded via reflection?Candis
Also, can you guarantee that ThirdPartyLibrary will be available to load? Is it part of the application that will consume your library?Candis
My code is loaded by runtime injection using the CLR profiling API. I can guarantee that there will be some version of ThirdPartyLibrary loaded, I just can't know which version until runtime. Also, I can test my code against all versions of ThirdPartyLibrary and guarantee that the interfaces I use are compatible/stable across them.Pelion
This any use? msdn.microsoft.com/en-us/library/…Candis
It would if I could resolve an assembly with a mis-matched version of the assembly. For example, MyClassLibrary could reference ThirdPartyLibrary v1 and then hook up an AssemblyResolve event handler. Then, when my code was executed inside an application that depended on ThirdPartyLibrary v3 my AssemblyResolve handler would be called and I could return ThirdPartyLibrary v3. If that works then yes, I could use that. I don't think that will work though?Pelion
AssemblyResolve can return a different version - I've added an additional answer with details and a demo.Candis

© 2022 - 2024 — McMap. All rights reserved.