【问题标题】:Structs, Interfaces and Boxing [duplicate]结构、接口和装箱
【发布时间】:2011-03-03 05:50:33
【问题描述】:

可能重复:
Is it safe for structs to implement interfaces?

获取此代码:

interface ISomeInterface
{
    public int SomeProperty { get; }
}

struct SomeStruct : ISomeInterface
{
    int someValue;

    public int SomeProperty { get { return someValue; } }

    public SomeStruct(int value)
    {
        someValue = value;
    }
}

然后我在某处这样做:

ISomeInterface someVariable = new SomeStruct(2);

SomeStruct 在这种情况下是否被装箱?

【问题讨论】:

    标签: c# struct interface value-type boxing


    【解决方案1】:

    Jon 的观点是正确的,但作为旁注,该规则有一个轻微的例外;仿制药。如果您有where T : ISomeInterface,那么这是约束,并使用special opcode。这意味着该界面可以在装箱的情况下使用。例如:

    public static void Foo<T>(T obj) where T : ISomeInterface {
        obj.Bar(); // Bar defined on ISomeInterface
    }
    

    涉及装箱,即使对于值类型T。但是,如果(在同一个Foo)你这样做:

    ISomeInterface asInterface = obj;
    asInterface.Bar();
    

    然后那个盒子像以前一样。 约束 only 直接适用于T

    【讨论】:

    • hai,不会被装箱,因为解析完所有泛型后调用的方法是void Foo(SomeStruct obj)而不是void Foo(ISomeInterface obj)
    • @Sekhat:泛型参数在运行时解析,因此编译器不知道使用值类型调用该方法。
    • @Sekhat - 扩展@adrianm 的观点:所有调用者都使用相同的IL。每个值类型参数都单独进行 JIT 处理,但所有 ref 类型共享一个 JIT。编译器与此无关; .NET 泛型是运行时的,而不是编译时的。在任何情况下,签名都是 Foo(T obj)。
    • 我从未说明泛型是在编译时还是运行时解析的。然而,它们在某些时候得到了解决。 Foo (T obj) 比 Foo(ISomeInterface interface) 更适合 SomeStruct,因为泛型最终被解析为 Foo(SomeStruct obj)。
    • @MarcGravell call explicitly implemented interface method of struct without boxing 另一个关于这种情况的问题
    【解决方案2】:

    是的,是的。基本上只要你需要一个reference并且你只有一个值类型的值,这个值就会被装箱。

    这里ISomeInterface是一个接口,是一个引用类型。因此someVariable 的值始终是一个引用,因此必须将新创建的 struct 值装箱。

    【讨论】:

    • 我是这么认为的。不完全确定是什么让我怀疑会是这样。只是想我会把它扔在这里,以防其他人有奇怪的疑惑。
    • 给一个男人一个得到答案的工具(红门反射器),他就会得到终生的答案。但是只要给他一个答案,他就会带着更多的问题和更多的 SO 代表点再次回来......
    • @Ben:另一方面,给男人一个工具,他们每次不确定时都必须检查它。给男人一个解释,他们就能自己推理。
    • 还要注意我的单独回复中提到的轻微边缘情况。
    • @ben dilasm 实际上在乔恩回答之前确实为我回答了这个问题。虽然只是把问题扔在那里已经给出了额外的答案。 Marc 的回答显示了它如何似乎您使用一个接口,该接口是一个不会被装箱的结构,并且可能是前一段时间我看到了可能已经发生的行为是什么引发了我的问题。
    【解决方案3】:

    我添加这个是为了希望更多地了解 Jon 和 Marc 提供的答案。

    考虑这种非泛型方法:

    public static void SetToNull(ref ISomeInterface obj) {
        obj = null;
    }
    

    嗯...将ref 参数设置为空。那可能只是参考类型,对吗? (好吧,或者是Nullable&lt;T&gt;;但为了简单起见,我们忽略这种情况。)所以这个方法编译的事实告诉我们,声明为某种接口类型的变量必须被视为引用类型。

    这里的关键短语是“声明为”:考虑这种调用上述方法的尝试:

    var x = new SomeStruct();
    
    // This line does not compile:
    // "Cannot convert from ref SomeStruct to ref ISomeInterface" --
    // since x is declared to be of type SomeStruct, it cannot be passed
    // to a method that wants a parameter of type ref ISomeInterface.
    SetToNull(ref x);
    

    当然,您不能将上述代码中的x 传递给SetToNull 的原因是x 需要声明为ISomeInterface 才能传递ref x -- 不是,因为编译器神奇地知道SetToNull 包含obj = null 行。但在某种程度上强化了我的观点:obj = null 行是合法的作为方法的ISomeInterface

    换句话说,如果一个变量被声明为ISomeInterface,它可以被设置为null,纯粹而简单。那是因为接口是引用类型——因此,将对象声明为接口并将其分配给值类型的对象会框出该值。

    现在,另一方面,考虑这个假设的泛型方法:

    // This method does not compile:
    // "Cannot convert null to type parameter 'T' because it could be 
    // a non-nullable value type. Consider using 'default(T)' instead." --
    // since this method could take a variable declared as, e.g., a SomeStruct,
    // the compiler cannot assume a null assignment is legal.
    public static void SetToNull<T>(ref T obj) where T : ISomeInterface {
        obj = null;
    }
    

    【讨论】:

    • 这与值类型和引用类型无关,与方差有关。
    • @Ben:我猜你是因为我的ref 示例而说的,我犹豫是否将其包括在内,因为我认为它可能有点令人困惑。但我的观点是,如果一个变量被声明为ISomeInterface,它可以设置为null,这仅适用于引用类型。因此,将ISomeInterface 变量设置为值类型的对象会导致装箱。这确实与值类型和引用类型有很大关系。如果变量被声明为特定的值类型,则该变量不能设置为 null。
    【解决方案4】:

    MSDN documentation 告诉我们结构是值,而不是引用类型。在与object 类型的变量进行转换时,它们会被装箱。但这里的中心问题是:接口类型的变量呢?由于接口也可以由类实现,所以这必须等同于从值转换为引用类型,正如 Jon Skeet 已经说过的那样,因此会发生装箱。 More discussion on an msdn blog.

    【讨论】:

    • 考虑这个问题的最简单方法是认识到每个变量、参数或字段除了接口的(可能为空的)组合之外还需要有一些具体的分配类型。如果没有其他具体类型可用,系统将假定一个对象引用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-15
    • 1970-01-01
    相关资源
    最近更新 更多