在这种情况下,C#generics是否会阻止结构的自动装箱?
通常,将结构S
视为接口I
将触发结构的自动装箱,如果经常这样做会对性能产生影响。 但是,如果我使用类型参数T : I
I编写一个generics方法并用S
调用它,那么编译器是否会省略装箱,因为它知道类型S
并且不必使用接口?
这段代码显示了我的观点:
interface I{ void foo(); } struct S : I { public void foo() { /* do something */ } } class Y { void doFoo(I i){ i.foo(); } void doFooGeneric(T t) where T : I { t.foo(); // <--- Will an S be boxed here?? } public static void Main(string[] args){ S x; doFoo(x); // x is boxed doFooGeneric(x); // x is not boxed, at least not here, right? } }
doFoo
方法在类型I
的对象上调用foo()
,所以一旦我们用S
调用它,那么S
就会被装箱。 doFooGeneric
方法做同样的事情。 但是,一旦我们用S
调用它,就不需要自动装箱,因为运行时知道如何在S
上调用foo()
。 但这会完成吗? 或者运行时会盲目地将S
到I
来调用接口方法?
void doFooGeneric(T t) where T : I { t.foo(); // <--- Will an S be boxed here?? }
那里的拳击将被避免!
结构类型S
是密封的。 对于上面的方法doFooGeneric
的类型参数T
值类型版本,C#编译器提供直接调用相关结构成员的代码,而不需要装箱。
哪个很酷。
有关技术细节,请参阅Sameer的答案。
好的,所以我想出了一个例子。 如果有人有一些我会对更好的例子感兴趣:
using System; using System.Collections.Generic; namespace AvoidBoxing { static class Program { static void Main() { var myStruct = new List { 10, 20, 30, }.GetEnumerator(); myStruct.MoveNext(); // moves to '10' in list // // UNCOMMENT ONLY *ONE* OF THESE CALLS: // //UseMyStruct(ref myStruct); //UseMyStructAndBox(ref myStruct); Console.WriteLine("After call, current is now: " + myStruct.Current); // 10 or 20? } static void UseMyStruct(ref T myStruct) where T : IEnumerator { myStruct.MoveNext(); } static void UseMyStructAndBox(ref T myStruct) { ((IEnumerator)myStruct).MoveNext(); } } }
这里myStruct
的类型是一个可变值类型,它保存一个返回List<>
的引用,并且还包含“counter”,它记住我们到目前为止已经达到的List<>
中的索引。
我不得不使用ref
,否则当传递给任一方法时,value-type将被值复制!
当我取消对UseMyStruct
(仅)的调用取消注释时,此方法将我们的值类型中的“计数器”向前移动一个位置。 如果它在值类型的盒装副本中执行此操作,我们将不会在结构的原始实例中看到它。
要查看与boxing有什么区别,请尝试调用UseMyStructAndBox
(再次注释UseMyStruct
)。 它在演员表中创建一个框,并在一个副本上发生MoveNext
。 所以输出是不同的!
对于那些对ref
不满意(或对其感到困惑)的人,只需从方法中写出Current
。 然后我们可以摆脱ref
。 例:
static void F(T t) where T : IEnumerator { t.MoveNext(); // OK, not boxed Console.WriteLine(t.Current); } static void G(T t) where T : IEnumerator { ((IEnumerator )t).MoveNext(); // We said "Box!", it will box; 'Move' happens to a copy Console.WriteLine(t.Current); }
由于Constrained Opcodes在第二种情况下发挥作用,因此将避免拳击。