c#编译器由于缓存委托而出现的奇怪行为

c#编译器由于缓存委托而出现的奇怪行为,c#,clr,compiler-optimization,C#,Clr,Compiler Optimization,假设我有以下程序: static void SomeMethod(Func<int, int> otherMethod) { otherMethod(1); } static int OtherMethod(int x) { return x; } static void Main(string[] args) { SomeMethod(OtherMethod); SomeMethod(x => OtherMethod(x)); So

假设我有以下程序:

static void SomeMethod(Func<int, int> otherMethod)
{
    otherMethod(1);
}

static int OtherMethod(int x)
{
    return x;
}

static void Main(string[] args)
{
    SomeMethod(OtherMethod);
    SomeMethod(x => OtherMethod(x));
    SomeMethod(x => OtherMethod(x));
}
static void somethod(Func otherMethod)
{
其他方法(1);
}
静态int-OtherMethod(intx)
{
返回x;
}
静态void Main(字符串[]参数)
{
somethod(OtherMethod);
somethod(x=>OtherMethod(x));
somethod(x=>OtherMethod(x));
}
我无法理解编译的il代码(它使用了太多的额外代码)。以下是简化版本:

class C
{
    public static C c;
    public static Func<int, int> foo;
    public static Func<int, int> foo1;
    static C()
    {
        c = new C();
    }
    C(){}
    public int b(int x)
    {
        return OtherMethod(x);
    }
    public int b1(int x)
    {
        return OtherMethod(x);
    }
}

static void Main()
{
    SomeMethod(new Func<int, int>(OtherMethod));
    if (C.foo != null)
        SomeMethod(C.foo)
    else
    {
        C.foo = new Func<int, int>(c, C.b)
        SomeMethod(C.foo);
    }
    if (C.foo1 != null)
        SomeMethod(C.foo1)
    else
    {
        C.foo1 = new Func<int, int>(c, C.b1)
        SomeMethod(C.foo1);
    }
}
C类
{
公共静态C;
公共静态Func-foo;
公共静态函数foo1;
静态C()
{
c=新的c();
}
C(){}
公共内部b(内部x)
{
返回其他方法(x);
}
公共内部b1(内部x)
{
返回其他方法(x);
}
}
静态void Main()
{
SomeMethod(新函数(其他方法));
如果(C.foo!=null)
somethod(C.foo)
其他的
{
C.foo=新函数(C,C.b)
somethod(C.foo);
}
如果(C.foo1!=null)
SomeMethod(C.1)
其他的
{
C.foo1=新函数(C,C.b1)
方法(C.1);
}
}

为什么编译器不创建静态相等方法
b/b1
?Equal意味着它们有相同的代码。你的问题是:为什么编译器没有意识到这两行

SomeMethod(x => OtherMethod(x));
SomeMethod(x => OtherMethod(x));
都是一样的,写这个作为

if ( delegate is not created ) 
  create the delegate and stash it away
SomeMethod( the delegate );
SomeMethod( the delegate );
??好吧,让我从几个方面回答这个问题

首先,是否允许编译器进行优化?对该规范要求C#编译器允许在单个委托中生成两个执行完全相同操作的lambda。事实上,您可以看到它已经部分地进行了优化:它创建每个委托一次,并将其保存起来,这样在以后再次调用代码时就不必再创建它了。请注意,如果代码只被调用一次,这将浪费内存

第二,进行缓存优化是否需要编译器?否。规范要求编译器只允许进行优化,但不要求进行优化

进行所需优化是否需要编译器?显然不是,因为它不是。这是允许的,也许未来版本的编译器会。编译器是开源的;如果您关心这个优化,那么编写它并提交一个pull请求

第三,是否可以进行您想要的优化?对编译器可以获取在同一方法中出现的所有lambda对,将它们编译为内部树格式,并进行树比较以查看它们是否具有相同的内容,然后为两者生成相同的静态备份字段

所以现在我们有一种情况:编译器被允许进行特定的优化,而它没有。你问“为什么不”?这是一个很容易回答的问题:除非有人花费大量的时间和精力:

  • 仔细设计优化:优化在什么条件下触发和不触发?优化应该有多普遍?你曾建议他们检测类似的lambda尸体,但为什么要到此为止?您有两个相同的代码语句,为什么不为这些语句生成一次而不是两次代码呢?如果你有一组重复的陈述呢?这里有大量的设计工作要做
  • 特别是,设计的一个重要方面是:用户是否可以“手动”合理地进行优化,同时保持代码可读性。在这种情况下,是的,他们可以,很容易。只需将重复的lambda分配给一个变量,然后使用该变量。一个能够自动完成用户关心的事情的优化实际上不是一个非常有趣或令人信服的优化
  • 你的例子微不足道;现实世界的代码并非如此。您建议的设计如何处理相同的嵌套lambda?等等
  • 您的优化是否会导致调试器中的代码行为“看起来很奇怪”?您可能已经注意到,当调试在启用优化的情况下编译的代码时,调试器的行为似乎很奇怪;这是因为生成的代码和原始代码之间不再存在清晰的映射。你的优化会让情况变得更糟吗?用户可以接受吗?调试器是否需要了解优化?如果是这样,则必须更改调试器。在这种情况下,可能不会,但这些是你必须问和回答的问题
  • 让专家对设计进行评审;这会占用他们的时间,并可能导致设计的更改
  • 对优化的优缺点进行评估——优化通常有隐藏的成本,就像我前面提到的内存泄漏一样。特别是,优化通常会排除其他可能更好的优化
  • 对该优化在全球范围内的总节约量进行估算。优化是否会影响真实世界的代码?它会改变代码的正确性吗?在世界任何地方,是否有任何生产代码会破坏这种优化,导致X公司的CTO致电微软的CTO要求修复?如果答案是肯定的,那么您可能不想进行此优化。C#不是玩具。数以百万计的人每天都依赖于它的正确运作
  • 在编译时进行优化的估计负担是什么?编译不必在击键之间进行,但必须非常快。任何在编译器的公共代码路径中引入超线性算法的行为都是不可接受的。您能否实现优化,使其在代码大小上是线性的?请注意,我之前绘制的算法——比较所有对——在代码大小上是超线性的。(练习:在所有lambda对上进行树比较的最坏情况渐近性能是什么?)
  • 实际上暗示