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