2010-06-01 10 views
3

Prenons l'exemple suivant:Comportement problématique de Linq Union?

public IEnumerable<String> Test() 
    { 
     IEnumerable<String> lexicalStrings = new List<String> { "test", "t" }; 
     IEnumerable<String> allLexicals = new List<String> { "test", "Test", "T", "t" }; 

     IEnumerable<String> lexicals = new List<String>(); 
     foreach (String s in lexicalStrings) 
      lexicals = lexicals.Union (allLexicals.Where (lexical => lexical == s)); 

     return lexicals; 
    } 

je l'espérais pour produire « test », « t » en sortie, mais il n'a pas (La sortie est seulement « t »). Je ne suis pas sûr, mais peut devoir faire quelque chose avec le traitement différé. Des idées pour que cela fonctionne ou pour une bonne alternative?

Editer: Veuillez noter qu'il ne s'agit que d'un exemple simplifié. lexicalStrings et allLexicals sont différents types dans le code d'origine. Donc je ne peux pas les combiner directement.

Edit2 le problème à résoudre ressemble plus à ceci:

public IEnumerable<Lexical> Test() 
    { 
     IEnumerable<String> lexicalStrings = new List<String> { "test", "t" }; 
     IEnumerable<Lexical> allLexicals = new List<Lexical> { ... }; 

     IEnumerable<Lexical> lexicals = new List<Lexical>(); 
     foreach (String s in lexicalStrings) 
      lexicals = lexicals.Union (allLexicals.Where (lexical => lexical.Text == s)); 

     return lexicals; 
    } 

Répondre

3

Vous utilisez mauvaise opération comme autre réponse expliquant. Mais encore, il est intéressant de savoir pourquoi votre code fonctionne mal, même si cela semble bien.

modifions votre application un peu:

 IEnumerable<String> lexicalStrings = new List<String> { "test", "t" }; 
     IEnumerable<String> allLexicals = new List<String> { "test", "Test", "T", "t" }; 

     IEnumerable<String> lexicals = new List<String>(); 
     foreach (String s in lexicalStrings) 
     { 
      lexicals = lexicals.Union(
       allLexicals.Where(
       lexical => 
       { 
        Console.WriteLine(s); 
        return lexical == s; 
       } 
       ) 
      ); 
     } 
     Console.WriteLine(); 
     foreach (var item in lexicals) 
     { 
     } 

ce que la sortie ne vous attendez-vous? voici le:

t 
t 
t 
t 
t 
t 
t 
t 

intéressant, n'est-ce pas?

maintenant laisser Modifions nouveau:

IEnumerable<String> lexicalStrings = new List<String> { "test", "t" }; 
    IEnumerable<String> allLexicals = new List<String> { "test", "Test", "T", "t" }; 

    IEnumerable<String> lexicals = new List<String>(); 
    foreach (String s in lexicalStrings) 
    { 
     string ls = s; 
     lexicals = lexicals.Union(
      allLexicals.Where(
      lexical => 
      { 
       Console.WriteLine(ls); 
       return lexical == ls; 
      } 
      ) 
     ); 
    }    
    foreach (var item in lexicals) 
    {     
    } 

maintenant la sortie et les résultats sont très bien:

test 
test 
test 
test 
t 
t 
t 
t 

Pourquoi est-il arrivé? Vous utilisez la fermeture - l'utilisation de var externe dans lambda interne. Puisque vous n'érigez pas réellement votre séquence, la valeur actuelle de s ne passe pas dans le lambda. foreach sorties et toutes les copies internes de s détiennent la valeur de la dernière itération. En cas de variable interne, ils contiennent des copies de valeurs créées pour chaque itération. Ce conflit vient du labyrinthe intérieur de LINQ. Si vous faites des choses comme List.AddRange résultat de la boucle à l'intérieur sera bien, car List.AddRange force l'itération.

+0

Merci pour l'explication et la solution facile! Up et répondu. – Foxfire

+0

J'ai pensé à utiliser AddRange, mais cela entraîne des doublons dans la liste (que l'Union devrait éviter automatiquement). – Foxfire

+0

@Foxfire vous devriez vraiment utiliser intersection, il résout le problème – Andrey

0

Est-ce que vous essayez d'atteindre?

lexicals.Union(allLexicals).Distinct(StringComparer.OrdinalIgnoreCase) 

EDIT:

Ou mieux encore comme suggéré @ Dave:

lexicals.Intersect(allLexicals, StringComparer.OrdinalIgnoreCase)

EDIT 2:

S'ils sont différents types un d'entre eux doit mettre en œuvre IEqualityComparer à l'autre. Passez ensuite cette classe à la méthode Intersection:

lexicals.Intersect(allLexicals, new MyCustomTComparer())

+0

J'ai ajouté quelques explications à la question car lexicalStrings et allLexicals sont des types différents dans le code original. – Foxfire

+0

cela devrait aller dans les commentaires, parce que la question est de savoir pourquoi le beau code ne fonctionne pas.et le problème est avec la fermeture – Andrey

1
public IEnumerable<Lexical> Test() 
{ 
    var lexicalStrings = new List<String> { "test", "t" }; 
    var allLexicals = new List<Lexical> { ... }; 

    var lexicals = new List<Lexical>(); 
    foreach (string s in lexicalStrings) 
    { 
     lexicals.AddRange(allLexicals.Where (lexical => lexical.Text == s)); 
    } 

    return lexicals; 
} 
+0

J'ai ajouté quelques explications à la question car lexicalStrings et allLexicals sont des types différents dans le code original. – Foxfire

+0

Ils doivent cependant partager un type de base commun, ou vous devez les convertir/convertir pour les associer dans votre résultat. Vous ne pouvez pas avoir un IEnumerable de deux types complètement différents. Vous pouvez passer un IEqualityComparer pour l'intersection, donc écrivez juste celui qui fait une comparaison d'égalité (c'est-à-dire lexical == s dans votre exemple) –

+0

Je ne veux pas les unir. Je veux une union des résultats d'un certain nombre de clauses "where" de linq (veuillez regarder l'exemple inférieur qui fonctionne dans gerenal, mais qui ne fournit pas la sortie attendue). – Foxfire