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



我在c#

中定义了这样的结构
[StructLayout(LayoutKind.Explicit)]
public struct MyUnion
{
    [FieldOffset(0)]
    public string MyString;
    [FieldOffset(0)]
    public Version MyVersion;
}

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

很好。不支持的唯一场景是将值类型字段与参考类型字段重叠。现在,GC无法再可靠地确定该值是否包含对象参考。CLR尽早猛击紧急停止,您将获得Typeloadexception。

这种联合的更通用的形式是歧视的联合。变体类型是规范示例。它具有另一个字段,指示字段的类型。实际上,您在示例中已经有一个,每个对象都有一个原本隐藏的字段,指示其类型。称为"类型句柄"或"方法表指针"。object.getType()使用它。垃圾收集器用来发现对象的实际类型的字段,声明的类型没有用,因为它可能是基类或接口。

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

相关内容

最新更新