为什么C#编译器在使用LINQ方法时会创建私有DisplayClass Any()以及如何避免它?

时间:2015-09-06 16:36:47

标签: c# performance linq instance any

我有这段代码(整个代码并不重要,但可以在this link上看到):

internal static class PlayCardActionValidator
{
    public static bool CanPlayCard(...)
    {
        // ...
        var hasBigger =
            playerCards.Any(
                c => c.Suit == otherPlayerCard.Suit
                     && c.GetValue() > otherPlayerCard.GetValue());
        // ...
    }
}

在反编译器(ILSpy)中打开代码之后,我注意到C#编译器存在新创建的类<>c__DisplayClass0_0

enter image description here

如果此代码对系统性能不重要,这对我来说不是问题。此方法被调用数百万次,垃圾收集器正在清理这些<>c__DisplayClass0_0实例,这会降低性能:

enter image description here

使用Any方法时,如何避免创建此类(他的实例和垃圾收集)?

为什么C#编译器会创建此类,是否可以使用Any()的替代方法?

2 个答案:

答案 0 :(得分:37)

要理解“显示类”,您必须了解闭包。你在这里传递的lambda是一个闭包,这是一种特殊类型的方法,可以从它所处方法的范围中神奇地拖拽状态并“关闭”它。

......除了当然没有魔法之类的东西。所有这个状态实际上都必须存在于真实的某个地方,这个地方与封闭方法有关并且可以从中获得。您将编程模式称为直接将状态与一个或多个方法关联的是什么?

这是正确的:类。编译器将lambda转换为闭包类,然后在托管方法中实例化类,以便托管方法可以访问类中的状态。

不发生这种情况的唯一方法是不使用闭包。如果这确实影响了性能,请使用旧式FOR循环而不是LINQ表达式。

答案 1 :(得分:23)

  

在使用Any方法时,如何避免创建此类(他的实例和垃圾收集)?

     

为什么C#编译器会创建这个类,是否可以使用任何替代的Any()?

其他海报已经解释了为什么部分,所以更好的问题是如何避免创建闭包?。答案很简单:如果lambda仅使用 传递的参数和/或常量,编译器将不会创建闭包。例如:

bool AnyClub() { return playerCards.Any(c => c.Suit == CardSuit.Club); }

bool AnyOf(CardSuit suit) { return playerCards.Any(c => c.Suit == suit); }

第一个不会创建闭包,而第二个则不会。

考虑到所有这些,并假设您不想使用 for / foreach 循环,您可以创建自己的扩展方法,类似于System.Linq.Enumerable中的扩展方法参数。对于这种特殊情况,这样的事情会起作用:

public static class Extensions
{
    public static bool Any<T, TArg>(this IEnumerable<T> source, TArg arg, Func<T, TArg, bool> predicate)
    {
        foreach (var item in source)
            if (predicate(item, arg)) return true;
        return false;
    }
} 

并将相关代码更改为:

var hasBigger =
    playerCards.Any(otherPlayerCard, 
        (c, opc) => c.Suit == opc.Suit
             && c.GetValue() > opc.GetValue());