Search code examples
c#.netremotingappdomain.net-assembly

Dynamically loading assembly: why this code works?


In my situation there are three components: Consumer class, IExposedIface interface and Exposed class implementing IExposedIface. Both Consumer and Exposed are linked statically with IExposedIface, but Consumer bears no compile-time reference to Exposed.

I am trying to come up with a scheme which would allow Consumer loading different versions of Exposed at runtime (depending on input data - let's say each input document carries an information about which version of Exposed should be used to process it). To achieve this, I started studying AppDomains and now I have a basic version working.

So far it seems to me there are two options when it comes to providing IExposedIface assembly to Exposed assembly.

  1. Having IExposedIface.dll only in Consumer's bin directory and handling AppDomain.AssemblyResolve event for the AppDomain in which I am creating an instance of Exposed

  2. Having IExposedIface.dll both in Consumer's bin directory as well as aside each Exposed.dll.

Now consider that I build Exposed against this IExposedIface:

public interface IExposedIface
{
    string SaySomething();    
}

and I build Consumer against this IExposedIface:

public interface IExposedIface
{
    string SaySomething();
    string SaySomethingDifferent();
}

In the first case, the exception

Exception: Method 'SaySomethingDifferent' in type 'Exposed.Exposed' from assembly 'Exposed, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.

is thrown in the moment I call appDomain.CreateInstanceAndUnwrap(...) to create an instance of Exposed in the freshly created AppDomain.

That looks reasonable to me.

But in the second case, appDomain.CreateInstanceAndUnwrap(...) goes through just fine and I can without problems call 'SaySomething()' method on the retrieved object. An exception

The method 'SaySomethingDifferent' was not found on the interface/type 'IExposedIface.IExposedIface, IExposedIface, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null'.

is only thrown when I actually call SaySomethingDifferent() in Consumer.

I was quite surprised that in this second case CLR had let me go so far...Could someone explain why this is possible?


Solution

    1. Case #1 means that Exposed.dll is binding against the wrong version of IExposedIface.dll - the metadata loader is able to detect this when loading the assemblies because it finds an unimplemented interface method.

    2. Case #2 (probably) means that you have the correct version of each IExposedIface.dll besides each Exposed.dll so each assembly can load within its own AppDomain. However AppDomain A has a different interface than AppDomain B which is only a problem when the call actually crosses the AppDomain border.

    I'd suggest not trying those binary compatibility games and rather do proper versioning (ie. create a new interface with the new methods, inheriting from the old interface, so the new version of IExposedIface.dll is really backwards compatible). Anything else is really hard to debug because you can accidently end up loading both versions of IExposedIface.dll if they are reachable for windows, and then you have two versions of a Type in the AppDomain causing no end of trouble ;)