当通过带有接口约束的泛型参数传递时,是否将值类型装箱? [英] Are value types boxed when passed as generic parameters with an interface constraint?
问题描述
例如:
void f (T val)其中T:IComparable
{
val.CompareTo(null) ;
}
void g()
{
f(4);
是 4
?我知道显式地将一个值类型转换为它实现触发器的接口装箱:
((IComparable)4).CompareTo空值); // Int324被装箱
我不知道是否传递一个值类型作为一个具有接口约束的泛型参数,就等于执行了一个强制转换 - 语言其中T是一个IComparable类型的转换建议,但只需将 T
转换为 IComparable
好像会打败通用的全部目的!
为了澄清,我想确定两者都不是这些事情发生在上面的代码中:
- 当
g
调用f(4)
,4
转换为IComparable
,因为存在IComparable
约束f
的参数类型。在f
,val.CompareTo(null)
中不会出现val 从 Int32
到IComparable
以调用CompareTo
。
但我想说一般情况下;不仅仅是 int
s和 IComparable
s发生了什么。
void Main()
{
((IComparable的)4).CompareTo(NULL);
f(4);
}
void f< T>(T val)其中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:调用UserQuery.f
f:
IL_0000:nop
IL_0001:ldarga.s 01
IL_0003:ldnull
IL_0004:约束。 01 00 00 1B
IL_000A:callvirt System.IComparable.CompareTo
IL_000F:pop
IL_0010:ret
很明显,拳击的出现和预期的一样,但是在 f
本身中没有显而易见的装扮* 或在
* 本MSDN文章讨论了约束
前缀,并指出,只要使用它与 callvirt
结合使用,就不会触发值类型的装箱操作被调用的方法在类型本身上实现(而不是基类)。我不确定的是,当我们到达这里时,类型是否仍然是值类型。
struct
被传递给泛型方法时,它将不会被装箱。 运行时为每个类型参数创建新方法。当您使用值类型调用泛型方法时,实际上是调用为各个值类型创建的专用方法。所以不需要装箱。
当调用不是直接在你的结构类型中实现的接口方法时,就会发生拳击。 Spec在这里调用它:
如果thisType是一个值类型,并且thisType没有实现方法
,则ptr被解除引用,boxed ,并将'this'指针作为
传递给callvirt方法指令。
最后一种情况只有在Object上定义方法时才会发生
ValueType或Enum,并且不会被thisType覆盖。在这种情况下,
装箱会导致创建原始对象的副本。但是,
因为Object,ValueType和Enum没有修改对象的
状态,所以无法检测到这一事实。
所以,只要你明确地[1]在你自己的结构体中实现了接口成员,就不会发生装箱。
1.不要与显式接口实现混淆。也就是说,你的接口方法应该在struct本身而不是它的基类型中实现。
(As a result of doing the research to answer this question, I (think I have!) determined that the answer is "no." However, I had to look in several different places to figure this out, so I think there is still value to the question. But I won't be devastated if the community votes to close.)
For example:
void f<T>(T val) where T : IComparable
{
val.CompareTo(null);
}
void g()
{
f(4);
}
Is 4
boxed? I know that explicitly casting a value type to an interface that it implements triggers boxing:
((IComparable)4).CompareTo(null); // The Int32 "4" is boxed
What I don't know is whether passing a value type as a generic parameter with an interface constraint is tantamount to performing a cast--the language "where T is an IComparable" sort of suggests casting, but simply turning T
into IComparable
seems like it would defeat the entire purpose of being generic!
To clarify, I would like to be sure neither of these things happens in the code above:
- When
g
callsf(4)
, the4
is cast toIComparable
since there is anIComparable
constraint onf
's parameter type. - Assuming (1) does not occur, within
f
,val.CompareTo(null)
does not castval
fromInt32
toIComparable
in order to callCompareTo
.
But I would like to understand the general case; not just what happens with int
s and IComparable
s.
Now, if I put the below code into LinqPad:
void Main()
{
((IComparable)4).CompareTo(null);
f(4);
}
void f<T>(T val) where T : IComparable
{
val.CompareTo(null);
}
And then examine the generated 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
It's clear that boxing occurs as expected for the explicit cast, but no boxing is obvious either in f
itself* or at its call site in Main
. This is good news. However, that's also just one example with one type. Is this lack of boxing something that can be assumed for all cases?
*This MSDN article discusses the constrained
prefix and states that using it in conjunction with callvirt
will not trigger boxing for value types as long as the called method is implemented on the type itself (as opposed to a base class). What I'm not sure of is whether the type will always still be a value type when we get here.
As you figured out already, When a struct
is passed to generic method, It will not be boxed.
Runtime creates new method for every "Type Argument". When you call a generic method with a value type, you're actually calling a dedicated method created for respective value type. So there is no need of boxing.
When calling the interface method which is not directly implemented in your struct type, then boxing will happen. Spec calls this out here:
If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.
This last case can occur only when method was defined on Object, ValueType, or Enum and not overridden by thisType. In this case, the boxing causes a copy of the original object to be made. However, because none of the methods of Object, ValueType, and Enum modify the state of the object, this fact cannot be detected.
So, as long as you explicitly[1] implement interface member in your struct itself, boxing will not occur.
How, when and where are generic methods made concrete?
1.Not to be confused with Explicit interface implementation. It is to say that your interface method should be implemented in struct itself rather than its base type.
这篇关于当通过带有接口约束的泛型参数传递时,是否将值类型装箱?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!