是什么导致C#中过多的匿名方法闭包(c__DisplayClass1)?

时间:2012-08-14 06:48:04

标签: c# .net linq foreach closures

Eqatec显示每次调用一个方法时调用的数千个匿名方法闭包,在我的程序中包含一个简单的LINQ'Where'语句。伪代码示例:

Class1
{
    //foo and bar are both EF model classes
    List<foo> aList; // n = 2000
    List<bar> bList; // n = ~4000

    void aMethod() 
    {  
        foreach (var item in aList)
        {
            Class2.DoSomeWork(item, bList);
        }
    }
}

Class2
{
    static void DoSomeWork(foo item, List<bar> bList)
    {
     var query = bList.where(x => x.prop1 == item.A && x.prop2 = item.B).toList(); // <--- Calls thousands of anonymous method closures each method call.

     if (query.any()) <--- Calls only 1 anonymous method closure.
        DoSomethingElse(); 
    } 
}

我不明白为什么2000次调用'DoSomeWork'会调用800万个匿名方法关闭(甚至1会导致数千个)。

作为修复,我只是在不使用LINQ的情况下重写了该语句,从而消除了对闭包的需求并使性能提高了10倍。

如果有人有一些他们想分享的理论,我仍然想明白为什么会出现这种情况。

2 个答案:

答案 0 :(得分:4)

我认为8M是指在闭包类上执行方法的次数,而不是创建的闭包实例的数量。首先,让我们编写代码:

class Class2
{
    public static void DoSomeWork(foo item, List<bar> bList)
    {
        var query = bList.Where(x => x.prop1 == item.A && x.prop2 == item.B)
                         .ToList();

        if (query.Any())
            DoSomethingElse();
    }
    static void DoSomethingElse() { }
}
class foo { public int A { get; set; } public int B { get; set; } }
class bar { public int prop1 { get; set; } public int prop2 { get; set; } }

现在,我们可以放弃原来的“//&lt; ---只调用1个匿名方法关闭”。注释,因为.Any()实际使用了 no 匿名方法闭包 - 只检查列表是否包含内容:不需要闭包。

现在;让我们手动重写闭包,以显示编译器中发生的事情:

class Class2
{
    class ClosureClass
    {
        public foo item; // yes I'm a public field
        public bool Predicate(bar x)
        {
            return x.prop1 == item.A && x.prop2 == item.B;
        }
    }
    public static void DoSomeWork(foo item, List<bar> bList)
    {
        var ctx = new ClosureClass { item = item };
        var query = bList.Where(ctx.Predicate).ToList();

        if (query.Any()) {
            DoSomethingElse();
        }
    }
    static void DoSomethingElse() { }
}

您可以看到每ClosureClass创建了一个DoSomeWork,它直接映射到唯一捕获的变量(item)在方法级别的作用域。 谓词ctx.Predicate)获得一次(仅限),但会为bList中的每个项目调用。确实,2000 * 4000是对方法的8M调用;但是,对方法的8M调用不一定很慢。

然而!我认为最大的问题是你正在创建一个新的列表来检查是否存在。你不需要那个。您可以通过提前移动Any来提高您的代码

if (bList.Any(x => x.prop1 == item.A && x.prop2 == item.B)) {
    DoSomethingElse();
}

现在这只会调用谓词足够多次,直到找到一个匹配,我们应该预期它会少于所有匹配;它也没有不必要的填写清单。

现在; ,手动执行此操作会更有效 ,即

bool haveMatch = false;
foreach(var x in bList) {
    if(x.prop1 == item.A && x.prop2 == item.B) {
        haveMatch = true;
        break;
    }
}
if(haveMatch) {
    DoSomethingElse();
}

但请注意,Anyforeach之间的此更改不是的关键差异; 严重的区别在于我删除了ToList()和“继续阅读,即使您已经找到匹配”。 Any(predicate)用法更加简洁,易于阅读等。这通常不是性能问题,我怀疑它是否在这里。

答案 1 :(得分:3)

在第

var query = bList.where(x => x.prop1 == item.A && x.prop2 = item.B).toList();

bList有4000个元素,x => x.prop1 == item.A && x.prop2 = item.B将被调用4000次。如果您希望懒惰地评估.Any(),请移除.ToList()