2008-11-24 5 views
13

Je travaille avec LINQ à des objets et j'ai une fonction où dans certains cas j'ai besoin de modifier la collection sous-jacente avant d'appeler Aggregate(...) puis de la remettre dans son état d'origine avant que la fonction renvoie les résultats de Aggregate(...). Mon code actuel ressemble à ceci:Quelque chose de mieux que .ToArray() pour forcer l'énumération de la sortie LINQ

bool collectionModified = false; 
if(collectionNeedsModification) 
{ 
    modifyCollection(); 
    collectionModified = true; 
} 

var aggregationResult = from a in 
          (from b in collection 
          where b.SatisfysCondition) 
          .Aggregate(aggregationFunction) 
         select a.NeededValue; 

if(collectionModified) 
    modifyCollection(); 

return aggregationResult; 

Cependant, comme il est écrit, si je modifie la collection, je vais obtenir le mauvais résultat parce que je suis en train de la collection dans son état d'origine avant aggregationResult soient dénombrés et LINQ les résultats sont paresseux-évalués. Ma solution actuelle est d'utiliser .ToArray() sur ma requête LINQ comme ceci:

var aggregationResult = (from a in 
          (from b in collection 
          where b.SatisfysCondition) 
          .Aggregate(aggregationFunction) 
         select a.NeededValue).ToArray(); 

La taille du tableau résultant sera toujours faible (100 articles) < donc la mémoire/temps de traitement ne sont pas une préoccupation. Est-ce la meilleure façon de gérer mon problème, ou existe-t-il un meilleur moyen de forcer l'évaluation d'une requête LINQ?

Répondre

15

Juste pour vérifier je vous comprends - vous voulez essentiellement parcourir tous les résultats, juste pour forcer les effets secondaires à avoir lieu?

Les effets secondaires sont généralement une mauvaise idée précisément parce que les choses sont plus difficiles à comprendre avec ce genre de logique. Cela dit, la meilleure façon de le faire et la force évaluation complète est probablement juste itérer à travers elle:

foreach (var result in aggregationResult) 
{ 
    // Deliberately empty; simply forcing evaluation of the sequence. 
} 

Sinon, vous pouvez utiliser LastOrDefault() pour éviter toute la copie impliquée dans ToArray(). Count() ira bien tant que le résultat ne met pas en œuvre IList<T> (ce qui implique un raccourci).

+2

Je préfère '.LastOrDefault()'. Notez que 'LastOrDefault' prend également un raccourci (dans ma version, .NET 4.5.2).Si la source est un 'IList <>', elle obtiendra d'abord 'Count', puis utilisera l'indexeur pour obtenir l'élément avec l'index' Count-1'. Par exemple cela fonctionne (utilise Moq): 'var listMock = nouveau Mock > (MockBehavior.Strict); listMock.Setup (x => x.Count) .Retours (666); listMock.Setup (x => x [665]). Renvoie ("dernier"); var last = listMock.Object.LastOrDefault(); ' –

0

Je ne pense pas qu'il y ait un problème avec votre approche si vous utilisez toujours le résultat (puisque votre jeu de résultats n'est pas grand, il ne consommera pas beaucoup de mémoire. n'utilisez jamais le résultat, cela imposera une perte de performance). Donc, oui, c'est la bonne façon de le faire.

3

Il est préférable d'éviter les fonctions d'effets secondaires telles que modifyCollection ci-dessus.

Une meilleure approche consiste à créer une fonction qui renvoie la collection (ou requête) modifiée, en laissant l'initiale intacte.

var modifiedCollection = ModifyCollection(collection, collectionNeedsModification); 

var aggregationResult = from a in 
         (from b in modifiedCollection 
         where b.SatisfysCondition) 
         .Aggregate(aggregationFunction) 
        select a.NeededValue; 

Lorsque ModifyCollection est une méthode qui renvoie la collection modifiée (ou interrogation) dans le paramètre en fonction du paramètre booléen collectionNeedsModification.

12

(Remarque: la saisie sans compilateur à portée de main, le code est donc non testé)

Si vous avez Reactive Extensions pour .NET comme une dépendance déjà vous pouvez utiliser Run():

aggregationResult.Run(); 

Mais il ne vaut peut-être pas la peine d'ajouter une dépendance pour cela.

Vous pouvez également implémenter la méthode Run vous-même comme une méthode d'extension:

public static MyLinqExtensions 
{ 
    public static void Run<T>(this IEnumerable<T> e) 
    { 
     foreach (var _ in e); 
    } 
} 
+0

Cela peut être utile lorsque l'énumérateur peut être' alive' (comme dans RX) ou bon comme réponse à une question. Mais pas pour une utilisation générale quand nous devons nous assurer que la collecte n'est pas paresseuse, je pense que «LastOrDefault» - qui prend raccourci - est meilleur. –

Questions connexes