2015-09-06 8 views
28

Ich habe diesen Code (der gesamte Code ist nicht wichtig, sondern kann auf this link zu sehen):Warum erstellt C# -Compiler private DisplayClass bei Verwendung der LINQ-Methode Any() und wie kann ich es vermeiden?

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

Nachdem Sie den Code in Decompiler (ILSpy) Öffnen zum Beispiel ich die Existenz bemerkt neu <>c__DisplayClass0_0 durch die erstellte Klasse C# Compiler:

enter image description here

Dies wäre kein Problem für mich, wenn dieser Code für die Leistung des Systems nicht entscheidend ist. Diese Methode wird millionenfach und der Garbage Collector ist die Reinigung dieser <>c__DisplayClass0_0 Instanzen genannt, die auf der Leistung verlangsamt:

enter image description here

Wie kann ich diese Klasse vermeiden zu schaffen (seine Instanzen und ihre Mülleinsammlungs), wenn die Verwendung von Any Methode?

Warum erstellt der C# -Compiler diese Klasse und gibt es eine Alternative Any() kann ich verwenden?

+4

Es muss den Code neu zu schreiben, ein sicheres Zuhause für die erfassten Variablen, otherPlayerCard zu finden und TrumpCard hier. Wenn Sie sie von lokalen Variablen in Felder umwandeln, kann deren Wert über den Methodenkörper hinaus erhalten bleiben. DisplayClass ist das sichere Zuhause. –

+11

Verwenden Sie nicht LINQ auf heißen Pfaden, das ist die Richtlinie für die Roslyn-Codebasis. – DaveShaw

+5

Normalerweise würde ich Micro-Optimierungen nicht empfehlen, aber wenn dieser Code ** Millionen ** mal ausgeführt wird, wäre das Refactoring, um es für die Geschwindigkeit zu optimieren, die Lösung hier. LINQ ist langsam. –

Antwort

36

Um die "Display-Klasse" zu verstehen, müssen Sie Verschlüsse verstehen. Das Lambda, das du hier passierst, ist ein Verschluss, eine spezielle Art von Methode, die den Zustand magisch aus dem Bereich der Methode zieht, in der sie sich befindet, und sie "schließt" sie.

... außer natürlich, dass es so etwas wie Magie nicht gibt. All dieser Zustand muss tatsächlich irgendwo real leben, irgendwo, was mit der Verschlussmethode verbunden ist und von dieser leicht zugänglich ist. Und wie nennst du das Programmiermuster, bei dem du den Status direkt mit einer oder mehreren Methoden assoziierst?

Das ist richtig: Klassen. Der Compiler wandelt das Lambda in eine Closure-Klasse um und instanziiert die Klasse innerhalb der Hosting-Methode, sodass die Hosting-Methode auf den Status in der Klasse zugreifen kann.

Die einzige Möglichkeit, dies nicht zu erreichen, besteht darin, keine Verschlüsse zu verwenden. Wenn dies die Leistung beeinträchtigt, verwenden Sie anstelle eines LINQ-Ausdrucks eine Old-School-Schleife FOR.

+11

_ "Jede ausreichend fortgeschrittene Technologie ist von Magie nicht zu unterscheiden." _ - Arthur C. Clarke – Gusdor

23

Wie kann ich vermeiden, diese Klasse (seine Instanzen und ihre Müllsammlung) zu erstellen, wenn Sie die Any-Methode verwenden?

Warum erstellt der C# -Compiler diese Klasse und gibt es eine Alternative von Any(), die ich verwenden kann?

Andere Plakate erklärt bereits die warum Teil, so dass die bessere Frage wäre Wie kann ich die Schaffung eines Verschlusses zu vermeiden?. Und die Antwort ist einfach: Wenn Lambda nur die übergebenen Parameter und/oder Konstanten verwendet, wird der Compiler keine Schließung erstellen. Zum Beispiel:

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

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

Die erste erstellt keine Schließung, während die zweite wird.

Mit allem im Auge, und vorausgesetzt, Sie wollen nicht für/foreach Schleifen verwenden, können Sie eigene Erweiterungsmethoden ähnlich in System.Linq.Enumerable die schaffen, sondern mit zusätzlichen Parametern. Für diesen speziellen Fall, so etwas wie dies funktionieren würde:

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; 
    } 
} 

und den Code in Frage ändern:

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

Hm, warum sollte keine Schließung für Parameter oder Instanzmitglieder erstellt werden? Ich weiß von keiner Möglichkeit, dass davon abgezogen werden könnte. – usr

+0

@ usr würde es eine statische/Instanzfunktion erstellen und einen Delegaten daran binden. keine Notwendigkeit für eine separate Klasse, da der gesamte Zustand aus Argumenten und/oder "this" besteht. –

+0

OK, keine Notwendigkeit für eine Klasse. Das ist richtig. Aber die Klasse hat keine Auswirkungen auf die Perfusion. Es ist teuer, neue Delegate- und Closure-Instanzen zu erstellen. Ich glaube, die Frage betrifft Perf. Er kümmert sich nicht um die verborgene Klasse. – usr