(作为回答这个问题的研究结果,我(我想我已经!)确定答案是"不".但是,我不得不在几个不同的地方看看这个,所以我觉得还有这个问题很有价值.但如果社区投票结束,我不会感到沮丧.)
例如:
void f(T val) where T : IComparable { val.CompareTo(null); } void g() { f(4); }
是4
盒装?我知道明确地将值类型转换为它实现触发装箱的接口:
((IComparable)4).CompareTo(null); // The Int32 "4" is boxed
我不知道的是,将值类型作为具有接口约束的泛型参数传递是否等于执行转换 - 语言"其中T是IC Comparable" 类似于建议转换,但只是转变T
为IComparable
似乎它会打败通用的全部目的!
为了澄清,我想确保在上面的代码中没有发生这些事情:
当g
调用时f(4)
,由于对参数类型有约束,因此4
被强制转换IComparable
为.IComparable
f
假定(1)不发生,内f
,val.CompareTo(null)
不投val
从Int32
到IComparable
为了调用CompareTo
.
但我想了解一般情况; 不只是int
s和IComparable
s 会发生什么.
现在,如果我将以下代码放入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
只要被调用的方法在类型本身上实现(而不是基类),使用它的前缀和状态就不会触发值类型的装箱.我不确定的是当我们到达这里时类型是否仍然是值类型.
正如你已经想到的那样,当a struct
传递给泛型方法时,它不会被装箱.
Runtime为每个"Type Argument"创建新方法.当您使用值类型调用泛型方法时,实际上是在调用为各个值类型创建的专用方法.所以不需要拳击.
当调用未在结构类型中直接实现的接口方法时,将发生装箱.Spec在这里调用它:
如果thisType是一个值类型而thisType没有实现方法,则ptr被解除引用,装箱,并作为'this'指针传递给callvirt方法指令.
最后一种情况只有在Object,ValueType或Enum上定义方法并且不被thisType覆盖时才会发生.在这种情况下,装箱会导致原始对象的副本.但是,由于Object,ValueType和Enum的方法都没有修改对象的状态,因此无法检测到此事实.
因此,只要您明确[1]在结构本身中实现接口成员,就不会发生装箱.
通用方法如何,何时何地具体化?
1.不要与Explicit接口实现混淆.这就是说你的接口方法应该在struct本身而不是它的基本类型中实现.