当通过带有接口约束的泛型参数传递时,是否将值类型装箱? [英] Are value types boxed when passed as generic parameters with an interface constraint?

查看:178
本文介绍了当通过带有接口约束的泛型参数传递时,是否将值类型装箱?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(作为研究回答这个问题的结果,我(认为我有!)确定答案是不。但是,我必须在几个不同的地方查找才能找出答案,所以我认为这个问题仍然有价值,但如果社群投票结束,我不会受到摧残。)



例如:

  void f (T val)其中T:IComparable 
{
val.CompareTo(null) ;
}

void g()
{
f(4);

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

 ((IComparable)4).CompareTo空值); // Int324被装箱

我不知道是否传递一个值类型作为一个具有接口约束的泛型参数,就等于执行了一个强制转换 - 语言其中T是一个IComparable类型的转换建议,但只需将 T 转换为 IComparable 好像会打败通用的全部目的!



为了澄清,我想确定两者都不是这些事情发生在上面的代码中:


  1. 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 本身中没有显而易见的装扮* 或在 Main 的呼叫站点。这是个好消息。但是,这也只是一种类型的例子。这是缺乏拳击的东西,可以假设所有情况下?






* 本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:

  1. When g calls f(4), the 4 is cast to IComparable since there is an IComparable constraint on f's parameter type.
  2. Assuming (1) does not occur, within f, val.CompareTo(null) does not cast val from Int32 to IComparable in order to call CompareTo.

But I would like to understand the general case; not just what happens with ints and IComparables.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆