如果T是int,拳击会在这里发生吗?

Val*_*zub 1 c# boxing

public static bool Equal<T>(T value, T match) {
           return Equals(value, match);
       }
Run Code Online (Sandbox Code Playgroud)

所以问题是如果T是int32是否会在这里装拳击或编译器将选择int32 Equals没有拳击?

Eri*_*ert 6

对于原始问题和Rango(基本上是正确的)答案的评论中存在一些混淆,所以我想我会清除它们.

首先,关于泛型如何在C#中工作的说明.泛型不是模板!

在C#中,泛型由C#编译器编译为一般IL,然后由抖动将IL重新编译为专用形式.例如,如果我们有一个方法M<T>(T t),那么C#编译器会将该方法及其主体编译一次到IL中.

当抖动出现时,一个电话M<string>,M<object>M<IEnumerable>会触发正好一个汇编; 抖动非常聪明,只要type参数是引用类型,它就可以将body编译成一个无论类型参数是什么都可以工作的形式.但是,M<int>M<double>每个人都可以编译成自己的汇编代码体.

请注意,抖动不知道C#的规则,C#执行重载决策.当C#编译器生成IL时,已经选择了每个方法调用的确切方法.所以如果你有:

static bool X(object a, object b) => object.Equals(a, b);
static bool X(int a, int b) => a == b;
static bool M<T>(T v, T m) => X(v, m);
Run Code Online (Sandbox Code Playgroud)

然后重载决策选择X(object, object)并编译代码,就像你写的:

static bool M<T>(T v, T m) => X((object)v, (object)m);
Run Code Online (Sandbox Code Playgroud)

如果T结果是int,则两个ints都被装箱object.

让我再次强调一下.当我们到达抖动时,我们已经知道X将要调用哪个; 该决定是在C#编译时完成的.C#编译器的原因是"我有两个Ts,我不知道它们可以转换为int,所以我必须选择对象版本".

这与C++模板代码形成对比,C++模板代码重新编译每个模板实例化的代码,并重新执行重载解析.

这样就回答了原来提出的问题.

现在让我们进入奇怪的细节.

当jit编译时M<int>,是否允许抖动通知M<int>调用X(object, object),然后调用object.Equals(object, object),这已知比较两个盒装整数的相等性,并直接生成直接比较两个整数的未编码形式的代码?

是的,允许抖动执行该优化.

它在实践中是否执行该优化?

据我所知.抖动确实执行了一些内联优化,但据我所知,它不会执行任何高级内联.

是否存在抖动在实践中没有拳击的情况?

是!

你能举一些例子吗?

当然可以.考虑以下可怕的代码:

struct S 
{
  public int x;
  public void M()
  {
      this.x += 1;
  }
}
Run Code Online (Sandbox Code Playgroud)

当我们这样做时:

S s = whatever;
s.M();
Run Code Online (Sandbox Code Playgroud)

怎么了? this值类型等同于类型的参数ref S.所以我们采取参考s,传递给M,等等.

现在考虑以下内容:

interface I
{
  void M();
}
struct S : I { /* body as before */ }
Run Code Online (Sandbox Code Playgroud)

现在假设我们这样做:

S s = whatever;
I i = s;
i.M();
Run Code Online (Sandbox Code Playgroud)

怎么了?

  • 转换sI拳击转换,因此我们分配一个框,制作框工具I,并在框中制作副本s.
  • 调用i.M()将框作为接收器传递给框中的实现I.然后将ref引用到s框中的副本,并将该ref作为thisto 传递M.

好吧,现在有点混淆了你.

void Q<T>(T t) where T : I
{
  t.M();
}
...
S s = whatever;
Q<S>(s);
Run Code Online (Sandbox Code Playgroud)

现在发生了什么?很显然,我们做的拷贝st并没有拳击; 两者都是类型S.但是:I.M期待一个类型的接收器I,并且t是类型的S.我们必须做我们之前做过的事吗?难道我们框t来实现一个箱子I,然后将箱子调用S.Mthis作为一个裁判箱子里吗?

不会.抖动生成的代码可以省略拳击并S.M直接使用ref tas 调用this.

这是什么意思?这意味着:

void Q<T>(T t) where T : I
{
  t.M();
}
Run Code Online (Sandbox Code Playgroud)

void Q<T>(T t) where T : I
{
  I i = t;
  i.M();
}
Run Code Online (Sandbox Code Playgroud)

是不同的!前者t因为拳击被跳过而发生变异.后面的框然后改变框.

这里的外卖应该是可变的价值类型是纯粹的邪恶,你应该不惜一切代价避免它们.正如我们所见,你可以很容易地进入你认为你应该改变一个副本的情况,但你正在改变你认为你正在改变原作的原始或更糟的情况,但你正在改变一个复制.

什么奇怪的魔法使这项工作?

使用sharplab.io并反汇编我给IL的方法.仔细阅读IL; 如果你有什么不明白的地方,请查阅.所有使这种优化工作的神奇机制都有详细记录.

抖动总是这样吗?

没有!(正如您所知,如果您按照我的建议阅读所有文档.)

但是,构建无法执行优化的方案有点棘手.我会把它留作谜题:

给我写一个程序,我们有一个S实现接口的结构类型I.我们约束类型参数TI,建设TS,并传递一个T t.我们称一种方法t作为接收器,并且抖动总是使接收器被装箱.

提示:我预测被调用方法的名称中有七个字母.我是对的吗?

挑战#2:一个问题:是否有可能使用我之前建议的相同技术证明拳击发生了?(该技术是:显示拳击必须发生,因为副本发生了突变,而不是原始.

是否存在不必要的抖动框?

是! 当我在编译器上工作时,抖动没有优化"盒子T到O,立即unbox O回到T"指令序列,有时需要C#编译器生成这样的序列以使验证器满意.我们要求实施优化; 我不知道是不是.

你能给我举个例子吗?

当然.假设我们有

class C<T>
{
  public virtual void M<U>(T t, U u) where U : T { }
}
class D : C<int>
{
  public override void M<U>(int t, U u)
  {
Run Code Online (Sandbox Code Playgroud)

好了,现在在这一点上,你知道唯一可能的类型Uint,所以t应该可以分配u,并且u应该可以分配给t吧?但是CLR验证程序没有这样看,然后你可以遇到编译器必须生成导致int被装箱object然后取消装箱的代码的U情况int,这样,往返是没有意义的.

这里有什么好吃的?

  • 不要改变值类型.
  • 泛型不是模板.重载分辨率只发生一次.
  • 抖动非常难以消除泛型中的装箱,但是如果a T被转换为object,则T真的,真正转换为object.