Search code examples
c#.netclr.net-assemblyvalue-type

Why does loading a class that contains a Value-Type field enforces the CLR to load that value type?


Suppose I have the following types in the following assemblies:

Assembly1:

public struct DependencyStruct { }
public class DependencyClass { }

Assembly2:

public class UsingDependency
{
    private DependencyStruct m_DependencyStruct; // having this will field will cause the loading of the DependencyStruct type (thus will cause an assembly binding request).
    private DependencyClass m_DependencyClass; // having this field will **not** cause the loading of the DependencyClass type.
}

Assembly3 (Executable)

public class Program
{
    static void Main(string[] args)
    {
       Assembly assembly = Assembly.LoadFrom("Assembly2.dll");
       Type[] types = assembly.GetTypes();
       Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
    }
}

When I run the following code, I will find Assembly2 and Assembly1 in the assemblies array.

If I comment out the m_DependencyStruct declaration, I will find only Assembly2 in the assemblies array.

Note that I'm not creating any instances in this code, just loading the types.

My questions are:

  1. Why does having a value type field cause the CLR to load the entire type (as opposed to a reference type which has a deferred loading)?

  2. Is there a way to defer that value-type loading? Using Lazy<DependencyStruct> m_LazyDependencyStruct or creating another wrapper class will work, but I'm curious if there's another way of doing this without changing the actual type.

Thanks!


Solution

  • This is pretty hard to nail down, the code in the CLR that loads types is a complicated mass of C++ code. One thing I see being done in the MethodTableBuilder::InitializeFieldDescs() method is that it also calculates the offsets of the fields in the class.

    Which requires knowing how much storage is required for each field. That's of course simple for fields of a reference type, it is just the size of a pointer. But not for fields of a value type, it requires loading the type of the field, recursing as necessary through its fields to calculate their size. Which of course has the side effect that you see the assembly that contains the value type loaded as well.

    Just an educated guess. You can have a look-see at the class.cpp source code file in the SSCLI20 distribution to look for yourself. A strong implementation detail, make sure you never care about this.