2012-02-25 9 views
28

cette méthode que j'ai écrit d'extension (qui compile):Aplatir IEnumerable <IEnumerable <>>; la compréhension des génériques

public static IEnumerable<J> Flatten<T, J>(this IEnumerable<T> @this) 
              where T : IEnumerable<J> 
{ 
    foreach (T t in @this) 
     foreach (J j in t) 
      yield return j; 
} 

Le code ci-dessous provoque une erreur de compilation (pas de méthode appropriée trouvée), pourquoi?:

IEnumerable<IEnumerable<int>> foo = new int[2][]; 
var bar = foo.Flatten(); 

Si je mets en œuvre l'extension comme ci-dessous, je reçois pas d'erreur de compilation:

public static IEnumerable<J> Flatten<J>(this IEnumerable<IEnumerable<J>> @this) 
{ 
    foreach (IEnumerable<J> js in @this) 
     foreach (J j in js) 
      yield return j; 
} 

Edit (2): Cette question que je considère répondu, mais il a soulevé une autre question concernant résolution de surcharge et contraintes de type. Cette question que je pose ici: Why aren't type constraints part of the method signature?

+1

Votre modification ne fonctionne pas parce que vous avez trop d'objets environnants. 'foo.Flatten , int>();' devrait fonctionner. – dlev

Répondre

65

D'abord, vous n'avez pas besoin Flatten(); cette méthode existe déjà et s'appelle SelectMany(). Vous pouvez l'utiliser comme ceci:

IEnumerable<IEnumerable<int>> foo = new [] { new[] {1, 2}, new[] {3, 4} }; 
var bar = foo.SelectMany(x => x); // bar is {1, 2, 3, 4} 

Deuxièmement, votre première tentative ne fonctionne pas parce que l'inférence de type générique ne fonctionne que sur la base des arguments à la méthode, pas de contraintes génériques liées à la méthode. Comme il n'y a pas d'argument qui utilise directement le paramètre générique J, le moteur d'inférence de type ne peut pas deviner ce que doit être J et ne pense donc pas que votre méthode soit candidate.

Il est édifiant de voir comment SelectMany() contourne ceci: il nécessite un argument supplémentaire Func<TSource, TResult>. Cela permet au moteur d'inférence de type de déterminer les deux types génériques, car ils sont tous les deux disponibles uniquement en fonction des arguments fournis à la méthode.

+1

@Daryl: Parce qu'il devrait être 'Aplatir , int> (toto)' – BrokenGlass

+2

@Daryl Les contraintes génériques ne sont pas considérées comme faisant partie d'une signature de méthode; pour * encore plus *, voir ce lien: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx – dlev

+1

@Daryl : Non - il y a certainement une courbe d'apprentissage ici, ce n'est de loin pas l'aspect le plus facile à comprendre.Juste essayer de le maîtriser vous met déjà au-dessus de 95% du repos ;-) – BrokenGlass

13

La réponse de dlev est bonne; Je pensais juste ajouter un peu plus d'informations. En particulier, je note que vous essayez d'utiliser des génériques pour implémenter une sorte de covariance sur IEnumerable<T>. En C# 4 et au-dessus, IEnumerable<T> est déjà covariant.

Votre deuxième exemple illustre cela. Si vous avez

List<List<int>> lists = whatever; 
foreach(int x in lists.Flatten()) { ... } 

puis tapez inférence raison pour laquelle List<List<int>> est convertible en IE<List<int>>, List<int> est convertible en , et donc, en raison de covariance, IE<List<int>> est convertible en IE<IE<int>>. Cela donne à l'inférence de type quelque chose à faire; il peut déduire que T est int, et tout est bon.

Cela ne fonctionne pas dans C# 3. La vie est un peu plus difficile dans un monde sans covariance, mais vous pouvez vous débrouiller avec une utilisation judicieuse de la méthode d'extension Cast<T>.

Questions connexes