Почему компилятор С# создает частный 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__DisplayClass0_0
, что замедляет производительность:
![введите описание изображения здесь]()
Как я могу избежать создания этого класса (его экземпляры и сбор мусора) при использовании метода Any
?
Почему компилятор С# создает этот класс и есть ли альтернатива Any()
, которую я могу использовать?
Ответы
Ответ 1
Чтобы понять "класс отображения", вы должны понимать закрытие. Лямбда, которую вы проходите здесь, - это закрытие, особый тип метода, который волшебным образом перетаскивается из состояния метода в него и "закрывает" его.
... кроме того, что нет такой вещи, как магия. Все это состояние должно фактически жить где-то реально, где-то, что связано с методом закрытия и легко доступно из него. И что вы называете шаблоном программирования, где вы напрямую связываете состояние с одним или несколькими методами?
Это право: классы. Компилятор преобразует лямбда в класс замыкания, а затем создает экземпляр класса внутри метода хостинга, поэтому метод хостинга может получить доступ к состоянию в классе.
Единственный способ избежать этого - не использовать закрытие. Если это действительно влияет на производительность, используйте цикл старой школы FOR
вместо выражения LINQ.
Ответ 2
Как я могу избежать создания этого класса (его экземпляры и сбор мусора) при использовании метода Any?
Почему компилятор С# создает этот класс и есть ли какая-либо альтернатива Any(), которую я могу использовать?
Другие плакаты уже объяснили часть почему, поэтому лучший вопрос будет Как я могу избежать создания закрытия?. И ответ прост: если лямбда использует только переданные параметры и/или константы, компилятор не создаст закрытие. Например:
bool AnyClub() { return playerCards.Any(c => c.Suit == CardSuit.Club); }
bool AnyOf(CardSuit suit) { return playerCards.Any(c => c.Suit == suit); }
Первое не будет создавать замыкание, а второе будет.
Учитывая все это и полагая, что вы не хотите использовать циклы для /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());