C# Assembly.Load vs Assembly.ReflectionOnlyLoad

2019-03-09 06:34发布

问题:

I'm trying to understand the differences between Assembly.Load and Assembly.ReflectionOnlyLoad.

In the code below I am attempting to find all of the objects in a given assembly that inherit from a given interface:

var myTypes = new List<Type>();

var assembly = Assembly.Load("MyProject.Components");

foreach (var type in assembly.GetTypes())
{
   if (type.GetInterfaces().Contains(typeof(ISuperInterface)))
   {
      myTypes.Add(type);
   }
}

This code works fine for me, but I was doing some research into other possibly better alternatives and came across Assembly.ReflectionOnlyLoad() method.

I assumed that since I'm not loading or executing any of the objects, essentially just querying on their definitions that I could use ReflectionOnlyLoad for a slight performance increase...

But it turns out that when I change Assembly.Load to Assembly.ReflectionOnlyLoad I get the following error when it calls assembly.GetTypes():

System.Reflection.ReflectionTypeLoadException:

Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.

I assumed that the above code was JUST doing reflection and "looking at" the library... but is this some sort of instance of the Heisenberg Uncertainty Principle whereby looking at the library and the objects in it is actually attempting to instantiate them in some way?

Thanks, Max

回答1:

As per Jon's reply, it would be helpful to know what's in LoaderExceptions. In lieu of this information, I think I can hazard a guess. From MSDN:

If the assembly has dependencies, the ReflectionOnlyLoad method does not load them. If you need to examine them, you must load them yourself.

You need to attach a handler to AppDomain.ReflectionOnlyAssemblyResolve to help the CLR load any dependencies of the assembly you're loading. Have you done this?



回答2:

I believe your general understanding of the differences between Load and ReflectionOnlyLoad is correct. The problem here (I think) is that even to simply load a type, the CLR needs to read the metadata from the assembly the type itself is defined in as well load the metadata from every assembly the type's ancestors are defined in. So, you need to call Assembly.ReflectionOnlyLoad on all assemblies that define types that are ancestors of the types you're loading.

To give an example, suppose you have the following class defined in assembly A.dll.

public class MyBase
{
   public void Foo() { }
}

and the following class defined in assembly B.dll.

public class MySubclass : MyBase
{
}

When you call Assembly.GetTypes on assembly B.dll, the CLR will try to load the type MySubclass and all of its members. Because the method Foo is defined in MyBase in assembly A.dll (and exists nowhere in the metadata of B.dll), the CLR will throw the type load exceptions if assembly A.dll has not been loaded.



回答3:

The ReflectionOnly methods are the only way you can load a specific Assembly on disk to examine without going via the usual Load/LoadFrom rules. For example, you can load a disk-based assembly with the same identity as one in the GAC. If you tried this with LoadFrom or LoadFile, the GAC assembly is ALWAYS loaded.

Additionally, you may not call GetCustomAttributes(...) on the return Assembly instance since this will attempt to instantiate the Attributes on the assembly, which are ReflectionOnly. You must use CustomAttributeData class's static methods for this.

No types in an assembly loaded via ReflectionOnly may be instantiated.



回答4:

No method can be executed from assembly, loaded with ReflectionOnlyLoad(), you will get InvalidOperationException. So this is safe way to determine assembly content using reflection.



回答5:

Another big difference between the two is Assembly.Load adds the assembly into the AppDomain where as Assembly.ReflectionOnlyLoad will not add the assembly in to the AppDomain

code to show in detail.

public void AssemblyLoadTest(string assemblyToLoad)
{
    var initialAppDomainAssemblyCount = AppDomain.CurrentDomain.GetAssemblies().Count(); //4

    Assembly.ReflectionOnlyLoad(assemblyToLoad);
    var reflectionOnlyAppDomainAssemblyCount = AppDomain.CurrentDomain.GetAssemblies().Count(); //4

    //Shows that assembly is NOT loaded in to AppDomain with Assembly.ReflectionOnlyLoad
    Assert.AreEqual(initialAppDomainAssemblyCount, reflectionOnlyAppDomainAssemblyCount); // 4 == 4

    Assembly.Load(assemblyToLoad);
    var loadAppDomainAssemblyCount = AppDomain.CurrentDomain.GetAssemblies().Count(); //5

    //Shows that assembly is loaded in to AppDomain with Assembly.Load
    Assert.AreNotEqual(initialAppDomainAssemblyCount, loadAppDomainAssemblyCount); // 4 != 5
}