在这种情况下,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() 。 但这会完成吗? 或者运行时会盲目地将SI来调用接口方法?

 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在第二种情况下发挥作用,因此将避免拳击。

Interesting Posts