2015-09-06 5 views
28

У меня есть этот код (весь код не важен, но можно увидеть на this link):Почему компилятор 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()); 
     // ... 
    } 
} 

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

enter image description here

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

enter image description here

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

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

+4

Необходимо переписать код, чтобы найти безопасный дом для захваченных переменных, otherPlayerCard и trumpCard здесь. Превращение их из локальных переменных в поля, чтобы их значение можно было сохранить за пределами тела метода. DisplayClass - это безопасный дом. –

+11

Не используйте LINQ для горячих путей, это политика на основе кода Roslyn. – DaveShaw

+5

Я бы обычно избегал рекомендовать микро-оптимизации, но если этот код запускается ** миллионы ** раз, рефакторинг этого для оптимизации его для скорости будет решением здесь. LINQ медленный. –

ответ

36

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

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

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

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

+11

_ «Любая достаточно продвинутая технология неотличима от магии». Артур Кларк – Gusdor

23

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

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

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

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

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

Первый не будет создавать закрытие, пока второй будет.

Со всем, что в виде, и если вы не хотите использовать для/Еогеаспа петли, вы можете создавать свои собственные методы расширения, аналогичные 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()); 
+1

Хм, почему закрытие не создавалось для параметров или членов экземпляра? Я не знаю, как это можно оттянуть. – usr

+0

@usr создаст функцию static/instance и привяжет к ней делегат. нет необходимости в отдельном классе, потому что все состояние состоит из аргументов и/или 'this'. –

+0

ОК, нет необходимости в классе. Это правда. Но класс имеет нулевое влияние. Создание новых экземпляров делегата и закрытия - вот что дорого. Я считаю, что речь идет о перфомансе. Его не интересует сам скрытый класс. – usr