通常,将结构S
视为接口I
会触发结构的自动装箱,如果经常这样做会对性能产生影响。但是,如果我编写一个采用类型参数T : I
的泛型方法并使用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 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
方法在foo()
类型的对象上调用I
,因此,一旦我们使用S
调用它,S
将被装箱。 doFooGeneric
方法做同样的事情。但是,一旦我们使用S
调用它,就不需要自动装箱,因为运行时知道如何在foo()
上调用S
。但这会完成吗?或者运行时会盲目地将S
框I
来调用接口方法吗?
答案 0 :(得分:7)
void doFooGeneric<T>(T t) where T : I {
t.foo(); // <--- Will an S be boxed here??
}
那里将避免拳击!
结构类型S
已被密封。对于上面的方法T
的类型参数doFooGeneric
的值类型版本,C#编译器提供直接调用相关结构成员的代码,而不进行装箱。
哪个很酷。
有关技术细节,请参阅Sameer的答案。
好的,所以我想出了一个例子。如果有人有,我会对更好的例子感兴趣:
using System;
using System.Collections.Generic;
namespace AvoidBoxing
{
static class Program
{
static void Main()
{
var myStruct = new List<int> { 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<T>(ref T myStruct) where T : IEnumerator<int>
{
myStruct.MoveNext();
}
static void UseMyStructAndBox<T>(ref T myStruct)
{
((IEnumerator<int>)myStruct).MoveNext();
}
}
}
此处myStruct
的类型是一个可变值类型,它将引用保存回List<>
,并且还包含“计数器”,用于记住我们List<>
中的哪个索引到现在为止。
我必须使用ref
,否则当传递给任何一个方法时,value-type将按值复制!
当我取消注释UseMyStruct
(仅限)时,此方法将“计数器”移动到我们的值类型中一个位置。如果它在值类型的盒装副本中执行,我们将不会在结构的原始实例中看到它。
要查看与拳击的区别,请尝试拨打UseMyStructAndBox
(再次评论UseMyStruct
)。它在演员表中创建一个框,MoveNext
发生在副本上。所以输出是不同的!
对于那些对ref
感到不满(或感到困惑)的人,只需在方法中写出Current
。然后我们可以摆脱ref
。例如:
static void F<T>(T t) where T : IEnumerator<int>
{
t.MoveNext(); // OK, not boxed
Console.WriteLine(t.Current);
}
static void G<T>(T t) where T : IEnumerator<int>
{
((IEnumerator<int>)t).MoveNext(); // We said "Box!", it will box; 'Move' happens to a copy
Console.WriteLine(t.Current);
}
答案 1 :(得分:3)
在第二种情况下Constrained Opcodes开始播放时,将避免拳击。