【问题标题】:Why does loading a class that contains a Value-Type field enforces the CLR to load that value type?为什么加载包含 Value-Type 字段的类会强制 CLR 加载该值类型?
【发布时间】:2014-04-13 16:21:01
【问题描述】:

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

组装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();
    }
}

当我运行以下代码时,我会在程序集数组中找到 Assembly2 和 Assembly1。

如果我注释掉 m_DependencyStruct 声明,我会在程序集数组中找到只有 Assembly2。

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

我的问题是:

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

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

谢谢!

【问题讨论】:

    标签: c# .net clr .net-assembly value-type


    【解决方案1】:

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

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

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

    【讨论】:

    • 感谢在 MethodTable::DoFullyLoad 找到它,它声明 // Fully load the exact field types for value type fields
    【解决方案2】:

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

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

    【讨论】:

    • 他只是加载类型,没有分配任何内容。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      另一方面,引用类型是“通过引用”传递的——容器类唯一需要知道的是引用的大小,这对于所有引用类型都是相同的。在您实际尝试使用它之前,CLR 不需要了解更多关于引用类型的信息。引用类型的默认值不会“使用”特定于该类型的任何内容,因此无需在稍后加载元数据。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-05-10
        • 1970-01-01
        • 2015-10-31
        • 2020-02-15
        • 2012-10-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多