Почему компилятор C# создает закрытый DisplayClass при использовании метода LINQ Any() и как я могу избежать этого?

У меня есть этот код (весь код не важен, но можно увидеть по этой ссылке):

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

Например, после открытия кода в декомпиляторе (ILSpy) я заметил существование вновь созданного класса <>c__DisplayClass0_0 компилятором C#:

Это не было бы проблемой для меня, если бы этот код не был критичным для производительности системы. Этот метод вызывается миллионы раз, и сборщик мусора очищает эти <>c__DisplayClass0_0 случаи, которые замедляют производительность:

Как я могу избежать создания этого класса (его экземпляры и сборщик мусора) при использовании Any метод?

Почему компилятор C# создает этот класс и есть ли альтернатива Any() Я могу использовать?

2 ответа

Решение

Чтобы понять "класс отображения", вы должны понимать замыкания. Лямбда, которую вы передаете здесь, является замыканием, особым типом метода, который волшебным образом перетаскивает состояние из области действия метода, в котором он находится, и "замыкает" его.

... кроме, конечно, что нет такой вещи, как магия. Все это состояние должно жить где-то реально, где-то, что связано с методом замыкания и легко доступно из него. И что вы называете паттерном программирования, где вы связываете состояние напрямую с одним или несколькими методами?

Это верно: классы. Компилятор преобразует лямбду в класс замыкания, а затем создает экземпляр класса внутри метода хостинга, чтобы метод хостинга мог получить доступ к состоянию в классе.

Единственный способ избежать этого - не использовать замыкания. Если это действительно влияет на производительность, используйте старую школу FOR цикл вместо выражения LINQ.

Как я могу избежать создания этого класса (его экземпляров и их сбора мусора) при использовании метода Any?

Почему компилятор C# создает этот класс и есть ли какая-либо альтернатива Any(), которую я могу использовать?

Другие постеры уже объяснили, почему, поэтому лучший вопрос будет: Как я могу избежать создания замыкания?, И ответ прост: если лямбда использует только переданные параметры и / или константы, компилятор не будет создавать замыкание. Например:

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());
Другие вопросы по тегам