当作为具有接口约束的通用参数传递时,值类型是否装箱?

(作为回答这个问题的研究结果,我(我想我有!)确定答案是“不”。但是,我不得不在几个不同的地方找出来解决这个问题,所以我认为还有如果社区投票结束,我不会感到沮丧。)

例如:

void f(T val) where T : IComparable { val.CompareTo(null); } void g() { f(4); } 

4盒装? 我知道显式地将值类型转换为它实现触发装箱的接口:

 ((IComparable)4).CompareTo(null); // The Int32 "4" is boxed 

我不知道的是,将值类型作为具有接口约束的generics参数传递是否等于执行强制转换 – 语言“其中T是IC可压缩”类型建议转换,但只是将T转换为IComparable似乎它会破坏通用的全部目的!

为了澄清,我想确保在上面的代码中没有发生这些事情:

  1. g调用f(4)4IComparableIComparable因为f的参数类型存在IComparable约束。
  2. 假设(1)没有发生,在f内, val.CompareTo(null)不会将valInt32IComparable以调用CompareTo

但我想了解一般情况; 不仅仅是intIComparable发生了什么。

现在,如果我将以下代码放入LinqPad:

 void Main() { ((IComparable)4).CompareTo(null); f(4); } void f(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一起使用callvirt不会触发值类型的装箱。 我不确定的是当我们到达这里时类型是否仍然值类型。

正如你已经想到的那样,当一个struct传递给generics方法时,它不会被装箱。

Runtime为每个“Type Argument”创建新方法。 当您使用值类型调用generics方法时,实际上是在调用为​​各个值类型创建的专用方法。 所以不需要拳击。

当调用未在结构类型中直接实现的接口方法时,将发生装箱。 Spec在这里调用它:

如果thisType是一个值类型而thisType没有实现方法,则ptr被解除引用,装箱,并作为’this’指针传递给callvirt方法指令。

最后一种情况只有在Object,ValueType或Enum上定义方法并且不被thisType覆盖时才会发生。 在这种情况下,装箱会导致原始对象的副本。 但是,由于Object,ValueType和Enum的方法都没有修改对象的状态,因此无法检测到此事实。

因此,只要您明确[1]在结构本身中实现接口成员,就不会发生装箱。

通用方法如何,何时何地具体化?

1.不要与Explicit接口实现混淆。 这就是说你的接口方法应该在struct本身而不是它的基本类型中实现。

一个简单的测试就是简单地创建一个可变结构,其中包含一个可以改变它的接口方法。 从generics方法调用该接口方法,并查看原始结构是否已发生变异。

 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 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没有被装箱。