为什么加载包含值类型字段的类会强制CLR加载该值类型

本文关键字:加载 类型 CLR 包含值 字段 为什么 | 更新日期: 2023-09-27 17:58:03

假设我在以下程序集中有以下类型:

组件1:

public struct DependencyStruct { }
public class DependencyClass { }

组件2:

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(可执行文件)

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

当我运行以下代码时,我将在assembly数组中找到Assembly2和Assembly1。

如果我注释掉m_DependencyConstruct声明,我将在assembly数组中只找到Assembly2。

请注意,我在这段代码中没有创建任何实例,只是加载类型。

我的问题是:

  1. 为什么具有值类型字段会导致CLR加载整个类型(而不是具有延迟加载的引用类型)?

  2. 有没有办法推迟该值类型的加载?使用Lazy<DependencyStruct> m_LazyDependencyStruct或创建另一个包装类是可行的,但我很好奇是否有其他方法可以在不更改实际类型的情况下做到这一点。

谢谢!

为什么加载包含值类型字段的类会强制CLR加载该值类型

这很难确定,CLR中加载类型的代码是大量复杂的C++代码。我在MethodTableBuilder::InitializeFieldDescs()方法中看到的一件事是,它还计算类中字段的偏移量。

这需要知道每个字段需要多少存储空间。对于引用类型的字段来说,这当然很简单,它只是一个指针的大小。但对于值类型的字段,它需要加载字段的类型,并根据需要在字段中递归以计算其大小。当然,这会产生副作用,即您会看到包含已加载值类型的程序集。

只是一个有根据的猜测。您可以查看SSCLI20发行版中的class.cpp源代码文件来查找自己。一个强大的实现细节,确保你永远不会关心这个。

之所以会发生这种情况,是因为结构变量的声明会导致结构被分配,因为结构不能为null,因此变量属于结构的默认版本。

类变量的声明可以为null,因此不需要分配类的出现。

由于类(引用类型)的默认值是null,因此不需要加载该类型来设置其默认值,而在struct作为值类型时,它应该初始化为new StructType,这是它的默认值,因此您可以看到此行为。

为了创建包含值类型字段的类型的实例,CLR需要知道在类的数据节内分配多少空间。值类型的全部目的是"按值"传递它们——整个类型都存在并直接访问。这意味着该字段的默认值需要将每个成员初始化为默认值,等等。这样做需要加载该类型的所有元数据。

另一方面,引用类型是"通过引用"传递的——容器类只需要知道引用的大小,这对所有引用类型都是相同的。CLR不需要了解更多关于引用类型的信息,直到您真正尝试使用它。引用类型的默认值不"使用"任何特定于该类型的内容,因此以后才需要加载元数据。