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

时间:2016-06-17 17:01:01

标签: c# .net clr unions

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

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

根据documentation for [FieldOffset],它会影响结构的非托管表示。但令我惊讶的是,它似乎在托管代码中同样有效:当我在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)

这仍然是"工作"从某种意义上说,保留了所包含对象的真实类型,但当然现在在编译器的思考和运行时的思考之间存在脱节,例如。

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");
}

1 个答案:

答案 0 :(得分:2)

这很好。唯一不支持的方案是将值类型字段与引用类型字段重叠。现在,GC无法再可靠地确定该值是否包含对象引用。 CLR会提前抢断紧急停止,您将获得TypeLoadException。

这种联盟的更一般形式是discriminated unionvariant type是规范的例子。它有另一个字段,指示字段的类型。实际上,在您的示例中已经有了这个,每个对象都有一个指示其类型的隐藏字段。被称为"类型句柄"或"方法表指针"。 Object.GetType()使用它。而垃圾收集器用来发现对象的实际类型的字段,声明的类型没有用,因为它可以是基类或接口。

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