An unexpected CLR behavior : Loading 2 times the same assembly in an AppDomain

While
developing a feature of NDepend we found out that the CLR can load 2 times the
same assembly in an AppDomain.

 

The feature
consists in including CQL constraints directly in the source code thanks to the
attribute NDepend.CQL.CQLConstraintAttribute found in the NDepend.CQL.dll. This
possibility represents a self-described way to express the architectural intentions and to make sure that your design remains clean. For example, in the following piece of code we make sure
that we will be advised if the class NamedPipeHelper is used outside the
classes ServerHost and ClientBase:


namepsace NDepend.AddIn.Common.InterProcessCommunication {

   [NDepend.CQL.CQLConstraint(@"// <Name>NamedPipeHelper restreint use</Name>

WARN IF Count > 0 IN SELECT TYPES WHERE

IsDirectlyUsing ""NDepend.AddIn.Common.InterProcessCommunication.NamedPipeHelper""

AND

!FullNameIs ""NDepend.AddIn.Common.InterProcessCommunication.ServerHost""

AND

!FullNameIs ""NDepend.AddIn.Common.InterProcessCommunication.ClientBase"" ")]

   public static class NamedPipeHelper {  }

}

 

We use both
the framework System.Reflection and Mono.Cecil to analyze assemblies.
Here is the code we used to extract CQLConstraint attributes on the type
referenced by the variable typeReflection and then, to get an XML
representation of the constraints:

 

System.Type typeReflection = null;

object[] listOfCQLConstraints =typeReflection.GetCustomAttributes(     typeof(NDepend.CQL.CQLConstraintAttribute), false);

string stringXml = (listOfCQLConstraints[0] as NDepend.CQL.CQLConstraintAttribute).ToXml();

// … here use stringXml

 

 

This code
can lead to load
2 times the assembly NDepend.CQL.dll in the current AppDomain.
Indeed, the JIT compiler triggers the load of $NDependInstallationPath$\Lib\NDepend.CQL.dll
the first time it figures out that the type NDepend.CQL.CQLConstraintAttribute
is used by a method. Then the call to the method Type.GetCustomAttributes(Type,bool) forces
to load the assembly $assembliesAnalyzedPath$\NDepend.CQL.dll because it is the
one that is referenced by the analyzed assemblies (the one that contains
typeReflection).

 

While this
behavior seems reasonable because the 2 versions of NDepend.CQL.dll can be
different (which is not the case here btw), it provokes a subtil bug in our code. There are
2 versions of the type CQLConstraintAttribute that are living in the AppDomain
and the version we pass to the method Type.GetCustomAttributes(Type,bool) is
not the same as the version that has been used to tag the types of the
assemblies analyzed. Hence, the method Type.GetCustomAttributes(Type,bool) won’t
return any
CQLConstraintAttribute object.

 

Hopefully,
we were able to correct this bug by using reflection. The idea is to fetch all
attributes tagging an analyzed type with the method
Type.GetCustomAttributes(bool) and then to find which attribute is tagged with
a type named “NDepend.CQL.CQLConstraintAttribute”. Then we just try to obtain a
method named “ToXml” and then we invoke it. 
The extra bonus we get with this code is that it works even if the
version of NDepend.CQL.dll that is referenced by the analyzed assembly is
different than the version of NDepend.CQL.dll loaded from the NDepend
installation assemblies.

 

object[] listOfCustomAttributes = typeReflection.GetCustomAttributes(false);

 

foreach (object obj in listOfCustomAttributes) {

   Type type = obj.GetType();

   if (type.FullName != “NDepend.CQL.CQLConstraintAttribute”) { continue; }

 

   MethodInfo methodToXml = type.GetMethod(“ToXml”);

   if (methodToXml == null) { continue; }

 

   object objectStringXml = methodToXml.Invoke(obj, new object[] { });

   if (objectStringXml == null) { continue; }

 

   string stringXml = objectStringXml as string;

   if (stringXml == null) { continue; }

   // … here use stringXml

}

 

 

 

 

   

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • andrew.flanagan@gmail.com

    Wow — thanks for this info. It was immensely frustrating when I was coding something related to this and got the error “Cannot cast My.Custom.Namespace.Object to My.Custom.Namespace.Object”. Your code makes a ton of sense and really helped me out…

  • Russ Gainford

    This is the first I’ve seen somebody post about this. We have this occur in our application where fusion binding competes with the reflection binding of our business objects.

    I remember spending a few hours the first time wondering why the if( myItem is MyType ) kept failing when the debugger showed the opposite. Finally found it by doing a quick watch with the Assembly.Location attribute.

    Nice post