在这种情况下,C# 泛型会阻止结构的自动装箱吗?
Do C# generics prevent autoboxing of structs in this case?
通常,将结构 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
方法在 I
类型的对象上调用 foo()
,所以一旦我们用 S
调用它,S
将得到盒装。 doFooGeneric
方法做同样的事情。然而,一旦我们用 S
调用它,可能不需要自动装箱,因为运行时知道如何在 S
上调用 foo()
。但这会完成吗?还是运行时会盲目地将S
框成一个I
来调用接口方法?
void doFooGeneric<T>(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<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<>
的引用,还保存了 "counter" ,它记住了 [=17] 中的索引=] 我们到现在为止。
我必须使用 ref
,否则当传递到任一方法时,值类型将按值复制!
当我取消注释对 UseMyStruct
的调用(仅)时,此方法将我们的值类型中的 "counter" 向前移动一个位置。如果它在值类型的盒装副本中这样做,我们将不会在结构的原始实例中看到它。
要查看装箱有何不同,请尝试调用 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);
}
将避免拳击,因为 Constrained Opcodes 在第二种情况下发挥作用。
通常,将结构 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
方法在 I
类型的对象上调用 foo()
,所以一旦我们用 S
调用它,S
将得到盒装。 doFooGeneric
方法做同样的事情。然而,一旦我们用 S
调用它,可能不需要自动装箱,因为运行时知道如何在 S
上调用 foo()
。但这会完成吗?还是运行时会盲目地将S
框成一个I
来调用接口方法?
void doFooGeneric<T>(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<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<>
的引用,还保存了 "counter" ,它记住了 [=17] 中的索引=] 我们到现在为止。
我必须使用 ref
,否则当传递到任一方法时,值类型将按值复制!
当我取消注释对 UseMyStruct
的调用(仅)时,此方法将我们的值类型中的 "counter" 向前移动一个位置。如果它在值类型的盒装副本中这样做,我们将不会在结构的原始实例中看到它。
要查看装箱有何不同,请尝试调用 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);
}
将避免拳击,因为 Constrained Opcodes 在第二种情况下发挥作用。