.NET 中的联合字段——它们真的可以在托管代码中工作吗?

Union fields in .NET - can they really work in managed code?

我在 C# 中定义了这样一个结构

[StructLayout(LayoutKind.Explicit)]
public struct MyUnion
{
    [FieldOffset(0)]
    public string MyString;
    [FieldOffset(0)]
    public Version MyVersion;
}

根据 documentation for [FieldOffset] 它影响结构的 unmanaged 表示。但令我惊讶的是,它似乎在托管代码中也能正常工作:当我在 dotTrace 中分析内存使用情况时,每个 MyUnion 实例都是一个指针的大小(x64 上为 8 字节)!这些值似乎仍然非常安全:

var stringInside = new MyUnion { MyString = "The string" };
var versionInside = new MyUnion { MyVersion = new Version(1, 2, 3, 4) };
Console.WriteLine(stringInside.MyString); // The string
Console.WriteLine(versionInside.MyVersion); // 1.2.3.4

等等,如果我访问了错误的字段怎么办?

var whatIsThis = stringInside.MyVersion;
var andThis = versionInside.MyString;
Console.WriteLine("{0} (type = {1})", whatIsThis, whatIsThis.GetType().FullName); // The string (type = System.String)
Console.WriteLine("{0} (type = {1})", andThis, andThis.GetType().FullName); // 1.2.3.4 (type = System.Version)

这仍然是 "works",因为所包含对象的真实类型被保留了,但当然现在编译器的想法和运行时的想法之间存在脱节,例如

Console.WriteLine("Compiler: is it a string? {0}", versionInside.MyString is string); // True
Console.WriteLine("Runtime: is it a version? {0}", versionInside.MyString.GetType() == typeof(Version)); // True

像这样使用联合有多危险?我可以依赖我在这里看到的行为吗?它是否可能以其他方式中断?特别是,这样使用代码安全吗?

if (versionInside.MyString.GetType() == typeof(string))
{
    Console.WriteLine("OK, it's a string, use the MyString field");
}
else
{
    Console.WriteLine("OK, it's a Version, use the MyVersion field");
}

这样就好了。唯一不支持的场景是将值类型字段与引用类型字段重叠。现在 GC 无法再可靠地确定该值是否包含对象引用。 CLR 提前紧急停止,你会得到一个 TypeLoadException。

这种联合的更一般形式是 discriminated union. The variant type 是典型的例子。它有另一个字段指示字段的类型。实际上,您的示例中已经有了这个,每个对象都有一个隐藏的字段来指示其类型。称为 "type handle" 或 "method-table pointer"。 Object.GetType() 使用它。垃圾收集器用来发现对象实际类型的字段,声明的类型没有用,因为它可能是基础 class 或接口。

当您重叠两个值类型值时,您将不可避免地 运行 遇到麻烦,现在如果没有另一个字段告诉您,您将无法再知道实际类型。如果你用错了,那么你只会读到垃圾。写入不会导致内存损坏,结构足够大以包含最大的类型。这种问题从来都不是那么难诊断或预测的。