2015-09-06 8 views
28

Ho questo codice (tutto il codice non è importante, ma può essere visto su this link):Perché il compilatore C# crea DisplayClass privato quando si utilizza il metodo LINQ Qualsiasi() e come posso evitarlo?

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

Dopo aver aperto il codice a decompilatore (ILSpy) per esempio ho notato l'esistenza di classe appena creata <>c__DisplayClass0_0 dal compilatore C#:

enter image description here

questo non sarebbe un problema per me se questo codice non è stato fondamentale per le prestazioni del sistema. Questo metodo viene chiamato milioni di volte e il garbage collector sta pulendo questi <>c__DisplayClass0_0 casi che rallenta le prestazioni:

enter image description here

Come posso evitare di creare questa classe (i suoi casi e la loro raccolta spazzatura) quando si utilizza il Metodo Any?

Perché il compilatore C# crea questa classe e c'è un'alternativa di Any() che posso usare?

+4

Ha bisogno di riscrivere il codice per trovare una casa sicura per le variabili catturati, otherPlayerCard e TrumpCard qui. Trasformandoli da variabili locali in campi in modo che il loro valore possa essere preservato oltre il corpo del metodo. DisplayClass è quella casa sicura. –

+11

Non utilizzare LINQ su percorsi caldi, questo è il criterio sulla base di codice di Roslyn. – DaveShaw

+5

Normalmente evito di raccomandare micro-ottimizzazioni, ma se questo codice viene eseguito ** milioni ** di volte, refactoring di questo per ottimizzarlo per la velocità sarebbe la soluzione qui. LINQ è lento. –

risposta

36

Per comprendere la "classe di visualizzazione" è necessario comprendere le chiusure. La lambda che passi qui è una chiusura , un tipo speciale di metodo che si trascina magicamente nello scopo del metodo in cui si trova e "lo chiude".

... tranne ovviamente che non esiste la magia. Tutto quello stato deve effettivamente vivere in un luogo reale, da qualche parte associato al metodo di chiusura e prontamente disponibile da esso. E come si chiama il modello di programmazione in cui si associa lo stato direttamente a uno o più metodi?

Proprio così: classi. Il compilatore trasforma il lambda in una classe di chiusura, quindi istanzia la classe all'interno del metodo di hosting in modo che il metodo di hosting possa accedere allo stato nella classe.

L'unico modo per non farlo è non utilizzare le chiusure. Se ciò influisce in modo significativo sulle prestazioni, utilizzare un ciclo FOR vecchia scuola anziché un'espressione LINQ.

+11

_ "Qualsiasi tecnologia sufficientemente avanzata è indistinguibile dalla magia." _ - Arthur C. Clarke – Gusdor

23

Come posso evitare di creare questa classe (le sue istanze e la loro garbage collection) quando si utilizza il metodo Qualsiasi?

Perché il compilatore C# crea questa classe e c'è qualche alternativa a Any() che posso usare?

Altri manifesti già spiegato la parte perché, quindi la domanda migliore sarebbe Come posso evitare la creazione di una chiusura?. E la risposta è semplice: se lambda usa solo i parametri passati e/o le costanti, il compilatore non creerà una chiusura. Ad esempio:

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

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

Il primo non creerà una chiusura mentre il secondo lo farà.

Con tutto questo in mente, e supponendo che non si desidera utilizzare per /foreach loop, è possibile creare propri metodi di estensione simili a quelli in System.Linq.Enumerable ma con parametri aggiuntivi. Per questo caso particolare, qualcosa di simile a questo dovrebbe funzionare:

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

e modificare il codice in questione:

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

Hm, perché non si dovrebbe creare una chiusura per parametri o membri di istanze? Non conosco il modo in cui questo potrebbe essere tirato. – usr

+0

@usr creerebbe una funzione statica/di istanza e vincolerebbe un delegato ad essa. non c'è bisogno di una classe separata perché l'intero stato consiste di argomenti e/o "questo". –

+0

OK, non c'è bisogno di una lezione. È vero. Ma la classe ha zero impatto perfetto. La creazione di nuove istanze di delegati e di chiusura è ciò che è costoso. Credo che la domanda riguardi il perf. Non è preoccupato per la classe nascosta stessa. – usr

Problemi correlati