(为了回答这个问题,我(认为我已经!)做了研究的结果,确定了答案是 "不"。然而,我不得不在几个不同的地方寻找这个问题,所以我认为这个问题还是有价值的。但如果社区投票关闭,我也不会一蹶不振)。)
比如说。
void f<T>(T val) where T : IComparable
{
val.CompareTo(null);
}
void g()
{
f(4);
}
是... 4
boxed? 我知道将一个值类型显式地投射到它所实现的接口上会触发装箱。
((IComparable)4).CompareTo(null); // The Int32 "4" is boxed
我不知道的是 把一个值类型作为通用参数传给一个接口约束是否等同于执行铸造 -- 语言中 "其中T是一个IComparable "就暗示了铸造,但只是简单地把 T
变成 IComparable
这似乎会破坏通用的整个目的!
要澄清的是,我想确定上述代码中这两件事都没有发生。
g
电话 f(4)
జజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజ 4
是投向 IComparable
既然有 IComparable
约束 f
的参数类型。f
, val.CompareTo(null)
不投 val
从 Int32
到 IComparable
为了调用 CompareTo
.但我想了解一般的情况,而不仅仅是发生了什么。int
s和 IComparable
s.
现在,如果我把下面的代码放到LinqPad中。
void Main()
{
((IComparable)4).CompareTo(null);
f(4);
}
void f<T>(T val) where T : IComparable
{
val.CompareTo(null);
}
然后检查生成的IL:
IL_0001: ldc.i4.4
IL_0002: box System.Int32
IL_0007: ldnull
IL_0008: callvirt System.IComparable.CompareTo
IL_000D: pop
IL_000E: ldarg.0
IL_000F: ldc.i4.4
IL_0010: call UserQuery.f
f:
IL_0000: nop
IL_0001: ldarga.s 01
IL_0003: ldnull
IL_0004: constrained. 01 00 00 1B
IL_000A: callvirt System.IComparable.CompareTo
IL_000F: pop
IL_0010: ret
很明显,在显式投射中,盒状结构会如期出现 但在显式投射中,盒状结构也不明显。f
本身* 或在其电话所在地 Main
. 这是一个好消息。然而,这也只是一种类型的一个例子。是否所有情况下都可以假设这种缺乏框框的情况?
*这篇MSDN文章 讨论了 constrained
前缀,并指出,将其与 callvirt
只要被调用的方法是在类型本身实现的(而不是基类),就不会触发值类型的框选。我不确定的是,该类型是否会始终保持 做 的一个值类型时,我们到这里。
正如你已经想明白了,当一个 struct
传递给通用方法,它将不会被装箱。
运行时为每个 "类型参数 "创建新的方法。当你调用一个带有值类型的泛型方法时,你实际上是在调用一个为相应值类型创建的专用方法。所以没有必要装箱。
当调用在结构类型中没有直接实现的接口方法时,就会发生框选。Spec在这里调用了这个。
如果thisType是一个值类型,而thisType没有实现方法,那么ptr就会被取消引用,装箱,并作为'this'指针传递给callvirt方法指令。
最后一种情况只有在方法被定义在Object、ValueType或Enum上,并且没有被thisType覆盖的情况下才会发生。在这种情况下,装箱会导致原始对象的复制。但是,由于Object、ValueType和Enum的方法都没有修改对象的状态,所以无法发现这个事实。
所以,只要你在结构本身中明确[1]实现接口成员,就不会发生装箱。
1.不能和Explicit接口实现混淆。就是说你的接口方法应该在struct本身中实现,而不是在它的基本类型中实现。
一个很简单的测试就是简单地创建一个可突变的struct,并带有一个突变它的接口方法。 从通用方法中调用该接口方法,看看原来的结构是否被突变了。
public interface IMutable
{
void Mutate();
int Value { get; }
}
public struct Evil : IMutable
{
public int value;
public void Mutate()
{
value = 9;
}
public int Value { get { return value; } }
}
public static void Foo<T>(T mutable)
where T : IMutable
{
mutable.Mutate();
Console.WriteLine(mutable.Value);
}
static void Main(string[] args2)
{
Evil evil = new Evil() { value = 2 };
Foo(evil);
}
这里我们看到打印出来的是9,这意味着实际的变量被突变了,而不是复制,所以在这个 struct
并没有被框住。
我把Servy给出的答案作为基础,我相信我的答案更有解释力,它证明了所声称的行为。
这段代码创建了一个结构和一个类,它们实现了一个接口方法。这个方法试图突变它们。代码从结构的通用方法中调用该接口方法,结构投向接口,然后再调用类。输出的结果很自明,显示出传递的结构体在没有投向接口之前是不装箱的。此外,我还添加了一些IL代码来查看何时发生盒化。
using System;
namespace ConsoleApp
{
public interface IMutable
{
void Mutate();
int Value { get; }
}
public struct EvilStruct: IMutable
{
public int value;
public void Mutate()
{
value++;
}
public int Value { get { return value; } }
}
public class EvilClass : IMutable
{
public int value;
public void Mutate()
{
value++;
}
public int Value { get { return value; } }
}
class Program
{
public static void Foo<T>(T mutable)
where T: IMutable
{
mutable.Mutate();
}
static void Main(string[] args)
{
EvilStruct Struct = new EvilStruct() { value = 1 };
Foo(Struct);
//Shows 1 after calling Mutate on value type
Console.WriteLine(Struct.Value);
IMutable YetAnotherStruct = new EvilStruct() { value = 1 };
Foo(YetAnotherStruct);
//Shows 2 after calling Mutate on value type
Console.WriteLine(YetAnotherStruct.Value);
EvilClass Class = new EvilClass() { value = 1 };
Foo(Class);
//Shows 2 after calling Mutate on ref type
Console.WriteLine(Class.Value);
Console.ReadLine();
}
}
}
输出:122
下面是Main方法的IL代码。你可以看到在IL_0038处发生了装箱。
Program.Main:
IL_0000: nop
IL_0001: ldloca.s 03
IL_0003: initobj UserQuery.EvilStruct
IL_0009: ldloca.s 03
IL_000B: ldc.i4.1
IL_000C: stfld UserQuery+EvilStruct.value
IL_0011: ldloc.3
IL_0012: stloc.0 // Struct
IL_0013: ldloc.0 // Struct
IL_0014: call UserQuery+Program.Foo<EvilStruct>
IL_0019: nop
IL_001A: ldloca.s 00 // Struct
IL_001C: call UserQuery+EvilStruct.get_Value
IL_0021: call System.Console.WriteLine
IL_0026: nop
IL_0027: ldloca.s 03
IL_0029: initobj UserQuery.EvilStruct
IL_002F: ldloca.s 03
IL_0031: ldc.i4.1
IL_0032: stfld UserQuery+EvilStruct.value
IL_0037: ldloc.3
IL_0038: box UserQuery.EvilStruct
IL_003D: stloc.1 // YetAnotherStruct
IL_003E: ldloc.1 // YetAnotherStruct
IL_003F: call UserQuery+Program.Foo<IMutable>
IL_0044: nop
IL_0045: ldloc.1 // YetAnotherStruct
IL_0046: callvirt UserQuery+IMutable.get_Value
IL_004B: call System.Console.WriteLine
IL_0050: nop
IL_0051: newobj UserQuery+EvilClass..ctor
IL_0056: dup
IL_0057: ldc.i4.1
IL_0058: stfld UserQuery+EvilClass.value
IL_005D: stloc.2 // Class
IL_005E: ldloc.2 // Class
IL_005F: call UserQuery+Program.Foo<EvilClass>
IL_0064: nop
IL_0065: ldloc.2 // Class
IL_0066: callvirt UserQuery+EvilClass.get_Value
IL_006B: call System.Console.WriteLine
IL_0070: nop
IL_0071: call System.Console.ReadLine
IL_0076: pop