gpt4 book ai didi

c# - 如果T为int,拳击会在这里发生吗?

转载 作者:太空宇宙 更新时间:2023-11-03 18:52:25 24 4
gpt4 key购买 nike

public static bool Equal<T>(T value, T match) {
return Equals(value, match);
}


所以问题是,如果T是int32,那么这里是否将装箱,否则编译器将选择不装箱的int32等于?

最佳答案

对原始问题和Rango的回答(基本正确)的评论中有些混淆,所以我认为我将这些内容清除掉。

首先,关于泛型如何在C#中工作的注释。泛型不是模板!

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

当出现抖动时,对M<string>M<object>M<IEnumerable>的调用将恰好触发一个编译。抖动非常聪明,并且只要类型实参是引用类型,它就可以将主体编译成可以工作的形式,而不管类型实参是什么。但是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);


然后重载解析选择 X(object, object)并像编写代码一样编译代码:

static bool M<T>(T v, T m) => X((object)v, (object)m);


如果 T证明是 int,则将两个 int装箱到 object

让我再次强调一下。到抖动时,我们已经知道要调用哪个 X了。该决定是在C#编译时做出的。 C#编译器的原因是“我有两个T,但我不知道它们可以转换为int,所以我必须选择对象版本”。

这与C ++模板代码相反,后者为每个模板实例重新编译该代码,并重新执行重载解析。

这样就回答了最初提出的问题。

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


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


是的,允许抖动执行优化。


  在实践中是否可以执行优化?


据我所知。抖动确实执行了一些内联优化,但是据我所知,它没有执行任何高级的内联。


  在实际操作中是否存在抖动导致拳击失败的情况?


是!


  你能举一些例子吗?


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

struct S 
{
public int x;
public void M()
{
this.x += 1;
}
}


当我们这样做时:

S s = whatever;
s.M();


怎么了?值类型中的 this等效于类型 ref S的参数。因此,我们引用 s,将其传递给 M,依此类推。

现在考虑以下几点:

interface I
{
void M();
}
struct S : I { /* body as before */ }


现在假设我们这样做:

S s = whatever;
I i = s;
i.M();


怎么了?


s转换为 I是装箱转换,因此我们分配一个框,使该框实现 I,并在该框中复制 s
调用 i.M()将框作为接收方传递到框中的 I实现。然后,将引用添加到框中的 s副本,并将该引用作为 this传递给 M


好吧,现在来了,这将使您困惑。

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


现在会发生什么?显然,我们将 s的副本复制到 t中,并且没有装箱;两者都是 S类型。但是: I.M期望接收器的类型为 I,而 t的类型为 S。我们必须做以前做过的事吗?我们是否将 t框到实现 I的框,然后该框调用 S.M,而 this是该框的引用?

不会。抖动生成的代码会绕过拳击,并直接以 S.M作为 ref t调用 this

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

void Q<T>(T t) where T : I
{
t.M();
}




void Q<T>(T t) where T : I
{
I i = t;
i.M();
}


是不同的!前者会变异 t,因为跳过了拳击。后面的框然后将其变异。

这里的重点应该是可变值类型是纯邪恶的,您应该不惜一切代价避免使用它们。正如我们所看到的,您可以很容易地进入认为您应该对副本进行变异的情况,但您正在对原始变异进行变异,或更糟糕的是,您认为您对原始物进行变异,但是您却对变异进行了变异。复制。


  什么奇异的魔法使这项工作有效?


使用sharplab.io并将我给出的方法反汇编为IL。仔细阅读IL。如果您不了解任何内容,请查找。充分证明了使该优化有效的所有神奇机制。


  抖动总是这样做吗?


没有! (您会知道是否按照我的建议阅读了所有文档。)

但是,构建无法执行优化的方案有些棘手。我将把它作为一个谜题:

给我写一个程序,其中我们有一个实现接口 S的结构类型 I。我们将类型参数 T约束为 I,并用 T构造 S,并传入 T t。我们用 t调用一个方法作为接收器,并且抖动总是使接收器处于装箱状态。

提示:我预计被调用方法的名称中包含七个字母。我说的对吗

挑战2:一个问题:是否有可能使用我之前建议的相同技术证明拳击发生? (这种技术是:表明必须进行装箱,因为副本发生了突变,而不是原始版本发生了突变。


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


是!当我在编译器上工作时,抖动并没有优化“从T到O的框,立即将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)
{


好的,现在您知道 U的唯一可能类型是 int,因此 t应该可分配给 u,而 u应该可分配给 t,对吗?但是CLR验证程序不会那样看,因此您可能会遇到编译器必须生成导致将 int装箱为 object然后取消装箱为 U(即 int)的代码的情况,因此往返是没有意义的。


  这是什么?



不要变异值类型。
泛型不是模板。重载解析仅发生一次。
抖动非常难以消除泛型中的装箱现象,但是,如果将 T转换为 object,则该 T确实会真正转换为 object

关于c# - 如果T为int,拳击会在这里发生吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54240396/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com