将结构体转换为通用接口时是否存在Boxing / Unboxing?

可能重复:
结构,接口和拳击

来自MSDN: http : //msdn.microsoft.com/en-us/library/yz2be5wk.aspx

Boxing是将值类型转换为类型对象由此值类型实现的任何接口类型的过程。

但是通用接口呢?

例如, int派生自IComparableIComparable

假设我有以下代码:

 void foo(IComparable value) { /* etc. */ } void bar(IComparable value) { /* etc. */ } void gizmo() { int i = 42; bar(i); // is `i` boxed? I'd say YES foo(i); // is `i` boxed? I fear it is (but I hope for NO) } 

bar (或任何采用非通用接口的function)是否意味着会有拳击?

foo (或任何在该类型上采用通用接口的函数)是否意味着会有拳击?

谢谢。

每次将结构强制转换为接口时,都会将其装箱。 IComparable 的目的是允许类似的东西:

 void bar(T value) where T : IComparable { /* etc. */ } 

当以这种方式使用时,struct将作为结构(通过generics类型参数)而不是作为接口传递,因此不必加框。 请注意,根据结构的大小,有时可能更好地通过值传递,有时通过引用传递,但当然如果使用现有的接口,如IComparable,则必须按接口要求传递。

首先,关于值类型,引用类型和装箱的简短(可能是不完整的)入门。

您可以判断某些内容是值类型,因为函数中所做的更改不会在函数外部保留。 调用该函数时会复制该对象的值,并在该函数的末尾抛弃该值。

您可以判断某些内容是引用类型,因为函数中所做的更改将保留在函数外部。 调用函数时不会复制对象的值,并且在该函数结束后存在该值。

如果装箱,则会制作一份副本,并在参考类型中就座。 它实际上从值类型更改为引用类型。

请注意,这一切都适用于实例化状态,即任何非静态成员数据。 静态成员不是实例状态,与引用类型,值类型或装箱无关。 不使用实例化状态的方法和属性(例如,仅使用局部变量或静态成员数据的方法和属性)在引用类型,值类型或发生装箱时的操作方式不同。

有了这些知识,以下是我们如何certificate在将结构转换为接口时发生装箱(通用或非通用)

 using System; interface ISomeInterface { void Foo(); T MyValue { get; } } struct SomeStruct : ISomeInterface { public void Foo() { this.myValue++; } public int MyValue { get { return myValue; } } private int myValue; } class Program { static void SomeFunction(ISomeInterface value) { value.Foo(); } static void Main(string[] args) { SomeStruct test1 = new SomeStruct(); ISomeInterface test2 = test1; // Call with struct directly SomeFunction(test1); Console.WriteLine(test1.MyValue); SomeFunction(test1); Console.WriteLine(test1.MyValue); // Call with struct converted to interface SomeFunction(test2); Console.WriteLine(test2.MyValue); SomeFunction(test2); Console.WriteLine(test2.MyValue); } } 

输出如下:

0
0
1
2

这意味着只有在进行转换时才会发生装箱:

  • 前两次调用在每次调用时进行装箱。
  • 后两个调用已经有一个盒装副本,每次调用时都不会发生装箱。

我不打算在这里复制所有代码,但是如果你将ISomeInterface更改为ISomeInterface ,你仍然会有相同的行为。

答案摘要

我对通用接口和装箱/拆箱的困惑来自于我知道C#generics使我们能够生成更高效的代码。

例如, int实现IComparable IComparable对我意味着:

  • IComparable将与旧的,前generics代码一起使用,但意味着装箱/拆箱
  • IComparable用于generics启用代码,据说可以避免装箱/拆箱

Eric Lippert的评论尽可能简单,清晰和直接:

通用接口类型是接口类型。 他们没有什么特别的神奇防止拳击

从现在开始,我毫无疑问地知道将一个结构体转换为界面将意味着拳击。

但是, IComparable如何比IComparable更有效?

这就是supercat的答案(由Lasse V. Karlsen编辑)向我指出仿制药比我想象的更像C ++模板:

IComparable的目的是允许以下内容:

  void bar(T value) where T : IComparable { /* etc. */ } 

这与以下内容截然不同:

  void bar(IComparable value) { /* etc. */ } 

甚至:

  void bar(IComparable value) { /* etc. */ } 

我的猜测是,对于第一个原型,运行时将为每个类型生成一个函数,因此,在处理结构时避免装箱问题。

然而,对于第二个原型,运行时将仅生成具有接口作为参数的函数,并且因此,当T是结构时进行装箱。 第三个函数只是选中结构,不多也不少。

(我猜这是与Java类型擦除generics实现相比,C#generics与C#结构相结合显示其优越性的地方。)

Merlyn Morgan-Graham的回答为我提供了一个我将在家里玩的测试的例子。 一旦我得到有意义的结果,我就会完成这个摘要(我想我会尝试使用pass-by-reference语义来看看它是如何工作的……)