public static bool Equal<T>(T value, T match) {
return Equals(value, match);
}
Run Code Online (Sandbox Code Playgroud)
所以问题是如果T是int32是否会在这里装拳击或编译器将选择int32 Equals没有拳击?
对于原始问题和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)
怎么了?
s为I拳击转换,因此我们分配一个框,制作框工具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)
现在发生了什么?很显然,我们做的拷贝s到t并没有拳击; 两者都是类型S.但是:I.M期待一个类型的接收器I,并且t是类型的S.我们必须做我们之前做过的事吗?难道我们框t来实现一个箱子I,然后将箱子调用S.M与this作为一个裁判箱子里吗?
不会.抖动生成的代码可以省略拳击并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.我们约束类型参数T到I,建设T有S,并传递一个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)
好了,现在在这一点上,你知道唯一可能的类型U是int,所以t应该可以分配u,并且u应该可以分配给t吧?但是CLR验证程序没有这样看,然后你可以遇到编译器必须生成导致int被装箱object然后取消装箱的代码的U情况int,这样,往返是没有意义的.
这里有什么好吃的?
T被转换为object,则T真的,真正转换为object.| 归档时间: |
|
| 查看次数: |
253 次 |
| 最近记录: |