2012-02-25 13 views
28

he escrito este método de extensión (que compila):Aplanar IEnumerable <IEnumerable <>>; la comprensión de los genéricos

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

El código siguiente provoca un error de tiempo de compilación (sin método adecuado encontró), por qué?:

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

Si implemento la extensión como la de abajo, me sale error de compilación tiempo:

(2)
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; 
} 

Editar: Esta pregunta considero respondió, pero se planteó otra pregunta respecto resolución de sobrecarga y restricciones de tipo. Esta pregunta puse aquí: Why aren't type constraints part of the method signature?

+1

Tu edición no funciona porque tiene demasiados rodea enumerable. 'foo.Flatten , int>();' debería funcionar. – dlev

Respuesta

65

Primero, no necesita Flatten(); ese método ya existe y se llama SelectMany(). Se puede utilizar de esta manera:

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

En segundo lugar, su primer intento no funciona debido a la inferencia de tipo genérico sólo funciona basa en los argumentos para el método, las restricciones no genéricos asociados con el método. Dado que no hay ningún argumento que utiliza directamente el parámetro genérico J, el motor de inferencia de tipos no puede adivinar lo J debería ser, y por lo tanto no piensa que su método es un candidato.

Es edificante ver cómo SelectMany() soluciona esto: requiere un argumento adicional de Func<TSource, TResult>. Esto permite que el motor de inferencia de tipos determine ambos tipos genéricos, ya que ambos están disponibles basados ​​únicamente en los argumentos provistos al método. respuesta

+1

@Daryl: Debido a que debería ser 'Aplanar < IEnumerable, int> (foo)' – BrokenGlass

+2

limitaciones @Daryl genéricos no se consideran parte de una firma de método; de forma más * *, consulta este enlace: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx – dlev

+1

@Daryl : No, definitivamente hay una curva de aprendizaje aquí, este no es el aspecto más fácil de entender de C#.Intentar dominarlo te pone por delante del 95% del resto ;-) – BrokenGlass

13

de dlev está bien; Solo pensé en agregar un poco más de información.

Específicamente, observo que está intentando utilizar genéricos para implementar una especie de covarianza en IEnumerable<T>. En C# 4 y superior, IEnumerable<T> ya es covariante.

Su segundo ejemplo ilustra esto. Si tiene

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

continuación, escriba inferencia razonable que se puede convertir en List<List<int>>IE<List<int>>, List<int> es convertible a , y por lo tanto, a causa de la covarianza, IE<List<int>> es convertible en IE<IE<int>>. Eso le da a la inferencia de tipo algo para continuar; puede inferir que T es int, y todo está bien.

Esto no funciona en C# 3. La vida es un poco más difícil en un mundo sin covarianza, pero puede pasar con el uso juicioso del método de extensión Cast<T>.

Cuestiones relacionadas