【问题标题】:Using "union struct" to avoid cast/box/unbox使用“联合结构”来避免强制转换/装箱/拆箱
【发布时间】:2016-06-27 10:15:20
【问题描述】:

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

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

在这个简化的例子中,类型字段可能会被省略,因为我们可以通过它们的 CLR 类型来区分字符串和整数。然而;我需要 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;
        }
    }
}

但是,这种方法需要一些“额外”的 IL 指令:

  • String-getter 需要 castclass 指令从对象转换为字符串。
  • Int64-getter 需要一个 unbox.any 指令才能从对象转换为长对象。
  • Int64-setter 需要 box 指令才能将 long 转换为 object。

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

因此我正在考虑使用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 编译器会因为某种原因而窒息?


在实际代码中;该结构将是不可变的。这些字段将是只读的,并且没有任何设置器。

首先,我认为这不会产生影响,因为它或多或少意味着设置器将移入构造器,每个值类型一个。

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

我需要这样做:

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

这意味着第二种方法也需要“额外”的 IL 指令。有三个额外的指令用于“默认”每个不会使用的字段。

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

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

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

【问题讨论】:

  • 这能解决什么问题?
  • @ErikPhilips:如果您指的是类型本身,它将提供一种处理这些受约束值的通用方式。在这种情况下,这很重要。如果你的意思是摆弄 IL 指令 - 那更有趣,因为我关心细节。
  • 为什么不在一个字段中存储 long 而在另一个字段中存储字符串?

标签: c# jit cil


【解决方案1】:

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

无法从程序集“MyAssembly”加载类型“MyValue”,因为它在偏移量 0 处包含对象字段,该对象字段未正确对齐或与非对象字段重叠 .

所以,我猜不可能在与值类型字段相同的偏移量上拥有引用类型字段。

故事结束。

我将留下这个问答(不是删除),但以防其他人好奇。

【讨论】:

  • CLR 通常不会让您读取对象引用的实际值(如果可行,可以通过这种方式完成),并且不会足够信任您以允许您这样做,即使你的意图是好的。
猜你喜欢
  • 1970-01-01
  • 2014-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-04
  • 2012-03-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多