使用 "union struct" 避免 cast/box/unbox

Using "union struct" to avoid cast/box/unbox

在某些情况下,我需要管理约束值。简化;假设我需要将值限制为字符串或 64 位整数。

为了这个目的;我正在考虑声明一种结构类型,其中一个字段用于存储值的类型,一个字段用于实际值。

在这个简化的例子中,类型字段可能会被省略,因为我们可以通过它们的 CLR 类型来区分字符串和整数。然而;我需要类型字段用于其他目的(多个 "constrained value type" 可以由单个 CLR 类型表示)。

直接的方法:

public struct MyValue
{
    private object _value;
    private MyValueType _type;

    public string String
    {
        get
        {
            // todo: check type
            return (string)_value;
        }

        set
        {
            // todo: validate value
            _type = MyValueType.String;
            _value = value;
        }
    }

    public long Int64
    {
        get
        {
            // todo: check type
            return (long)_value;
        }

        set
        {
            // todo: validate value
            _type = MyValueType.Int64;
            _value = value;
        }
    }
}

但是,这种方法需要一些 "extra" IL 指令:

这个结构的目的是强制约束,所以当它获取或设置一个值时,就知道它是正确的类型。

因此我正在考虑使用 FieldOffset 属性。像这样:

[StructLayout(LayoutKind.Explicit)]
public struct MyValue
{
    [FieldOffset(0)]
    private string _string;
    [FieldOffset(0)]
    private long _int64;

    [FieldOffset(8)]
    private MyValueType _type;

    public string String
    {
        get
        {
            // todo: check type
            return _string;
        }

        set
        {
            // todo: validate value
            _type = MyValueType.String;
            _string = value;
        }
    }

    public long Int64
    {
        get
        {
            // todo: check type
            return _int64;
        }

        set
        {
            // todo: validate value
            _type = MyValueType.Int64;
            _int64 = value;
        }
    }
}

使用这种方法,没有额外的装箱、拆箱或演员表说明。这让我觉得这种方法更好。

问题是:使用显式结构布局和字段偏移属性有什么缺点吗?

也许 JIT 编译器会因为某些原因而阻塞?


在真实代码中;该结构将是不可变的。这些字段将是只读的,并且不会有任何 setters。

首先,我不认为这会有什么不同,因为这或多或少意味着 setter 将移入构造函数,每个值类型一个。

但是;编译器要求 all 结构成员由构造函数初始化——不考虑它们具有相同的字段偏移量。

我需要做这样的事情:

public MyValue(string value)
{
    // todo: validate value
    _int64 = 0; // just to satisfy the compiler
    _string = value;
    _type = MyValueType.String;
}

public MyValue(long value)
{
    // todo: validate value
    _string = null; // just to satisfy the compiler
    _int64 = value;
    _type = MyValueType.Int64;
}

这意味着第二种方法也需要 "extra" IL 指令。 "defaulting" 每个不会被使用的字段有三个额外的说明。

例如:设置 _string = null 产生 ldarg.0ldnullstfld

这些额外的指令完全是浪费。如果我添加额外的字段,情况会变得更糟。

所以;还有一个问题:JIT 编译器是否足够聪明以忽略这些浪费的指令?

没关系。看来这无论如何也做不到。尝试加载这样的类型会导致 TypeLoadException 说出类似

的内容

Could not load type 'MyValue' from assembly 'MyAssembly' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.

所以,我想引用类型字段不可能与值类型字段位于相同的偏移量上。

故事结束。

我将离开这个问答(而不是删除),以防其他人好奇。