multiple c# linq interface ienumerable

c# - multiple - ¿Por qué IEnumerable<T>.ToList<T>() devuelve List<T> en lugar de IList<T>?



tolist() c# (10)

Creo que la decisión de devolver un List <> en lugar de un IList <> es que uno de los casos de uso más comunes para llamar a ToList es forzar la evaluación inmediata de toda la lista. Al devolver una Lista <> esto está garantizado. Con un IList <> la implementación aún puede ser floja, lo que vencería el propósito "principal" de la llamada.

El método de extensión ToList() devuelve una List<TSource> . Siguiendo el mismo patrón, ToDictionary() devuelve un Dictionary<TKey, TSource> .

Tengo curiosidad por saber por qué esos métodos no IList<TSource> sus valores de retorno como IList<TSource> e IDictionary<TKey, TSource> respectivamente. Esto parece incluso más extraño porque ToLookup<TSource, TKey> escribe su valor de retorno como una interfaz en lugar de una implementación real.

Al dotPeek la fuente de esos métodos de extensión usando dotPeek u otro decompilador, vemos la siguiente implementación (que muestra ToList() porque es más corta):

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) { if (source == null) throw Error.ArgumentNull("source"); return new List<TSource>(source); }

Entonces, ¿por qué este método escribe su valor de retorno como una implementación específica de la interfaz y no la interfaz en sí? El único cambio sería el tipo de devolución.

Tengo curiosidad porque las IEnumerable<> son muy consistentes en sus firmas, a excepción de esos dos casos. Siempre pensé que sería un poco extraño.

Además, para hacer las cosas aún más confusas, la documentación de ToLookup() indica:

Crea una búsqueda de un IEnumerable de acuerdo con una función de selector de tecla especificada.

pero el tipo de devolución es ILookup<TKey, TElement> .

En Edulinq , Jon Skeet menciona que el tipo de retorno es List<T> lugar de IList<T> , pero no toca el tema más.
La búsqueda exhaustiva no arrojó ninguna respuesta, así que aquí te pregunto:

¿Hay alguna decisión de diseño detrás de no escribir los valores de retorno como interfaces, o es simplemente casualidad?


En general, cuando llamas a ToList () en un método, estás buscando un tipo concreto; de lo contrario, el elemento podría permanecer como tipo IEnumerable. No necesita convertir a una Lista a menos que esté haciendo algo que requiera una lista concreta.


En mi opinión, devolver una List<T> se justifica por el hecho de que el nombre del método dice ToList . De lo contrario, debería llamarse ToIList . El objetivo de este método es convertir un IEnumerable<T> específico en el tipo específico List<T> .

Si tuviera un método con un nombre no específico como GetResults , un tipo de devolución como IList<T> o IEnumerable<T> me parecería apropiado.

Si Lookup<TKey, TElement> la implementación de la clase Lookup<TKey, TElement> con reflector, verás muchos miembros internal , que solo pueden acceder a LINQ. No hay constructor público y los objetos de Lookup son inmutables. Por lo tanto, no habría ninguna ventaja al exponer Lookup directamente.

Lookup<TKey, TElement> clase Lookup<TKey, TElement> parece ser una especie de LINQ-internal y no es para uso público.


Esta es una de las cosas comunes que los programadores tienen dificultades para entender sobre el uso de interfaces y tipos concretos.

Devolver una List<T> concreta List<T> que implemente IList<T> solo proporciona al consumidor del método más información . Esto es lo que implementa el objeto List (a través de MSDN):

[SerializableAttribute] public class List<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable

La devolución como una List<T> nos da la capacidad de llamar a los miembros en todas estas interfaces, además de la List<T> . Por ejemplo, solo podríamos usar List.BinarySearch(T) en una List<T> , ya que existe en List<T> pero no en IList<T> .

En general, para maximizar la flexibilidad de nuestros métodos, debemos tomar los tipos más abstractos como parámetros (es decir, solo las cosas que vamos a usar) y devolver el tipo menos abstracto posible (para permitir un objeto de devolución más funcional).


Este tipo de decisiones pueden parecer arbitrarias, pero supongo que ToList() devuelve List<T> lugar de una interfaz porque List<T> implementa IList<T> pero agrega otros miembros que no están presentes en un tipo normal IList<T> objeto.

Por ejemplo, AddRange() .

Vea lo que IList<T> debería implementar ( http://msdn.microsoft.com/en-us/library/5y536ey6.aspx ):

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable

Y List<T> ( http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx ):

public class List<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable

Tal vez su propio código no requiere IReadOnlyList<T> , IReadOnlyCollection<T> o ICollection , pero otros componentes en .NET Framework y otros productos pueden depender de un objeto de lista más especializado y es por eso que el equipo de desarrollo de .NET decidió no devolver una interfaz.

No sentir que siempre devuelven una interfaz es la mejor práctica. Es si su código o los de terceros requieren dicha encapsulación.


Hay una serie de ventajas al tener una List sobre un IList . Para empezar, List tiene métodos que IList no tiene. También sabe cuál es la implementación que le permite razonar sobre cómo se comportará. Usted sabe que puede agregar de manera eficiente hasta el final, pero no el comienzo, usted sabe que su indexador es muy rápido, etc.

No necesita preocuparse de que su estructura se cambie a LinkedList y arruine el rendimiento de su aplicación. Cuando se trata de estructuras de datos como esta, realmente es importante en bastantes contextos saber cómo se implementa su estructura de datos, no solo el contrato que sigue. Es un comportamiento que nunca debería cambiar.

Tampoco puede pasar un IList a un método que acepte una List , que es algo que puede ver bastante. ToList se usa con frecuencia porque la persona realmente necesita una instancia de List , para que coincida con una firma que no puede controlar, e IList no ayuda con eso.

Entonces nos preguntamos qué ventajas hay para regresar IList . Bueno, posiblemente podríamos devolver alguna otra implementación de una lista, pero como se mencionó antes, es probable que tenga consecuencias muy negativas, casi con toda seguridad mucho más de lo que se podría ganar con el uso de cualquier otro tipo. Puede que te dé la sensación de estar usando una interfaz en lugar de una implementación, pero incluso eso es algo que no creo que sea una buena mentalidad (en general o) en este contexto. Como regla, devolver una interfaz generalmente no es preferible a devolver una implementación concreta. "Sé liberal en lo que aceptas y específico en lo que brindas". Los parámetros de sus métodos deben, siempre que sea posible, ser interfaces que definan la menor cantidad de funcionalidad que necesita para que la persona que llama pueda pasar en cualquier implementación que haga lo que necesita de ella, y debe proporcionar una implementación lo más concreta posible. "permitido" ver para que puedan hacer tanto con el resultado como ese objeto sea capaz de hacer. Pasar una interfaz es restringir ese valor, que ocasionalmente solo es algo que desea hacer.

Entonces, ahora pasamos a "¿Por qué devolver ILookup y no Lookup ?" Bueno, primero de Lookup no es una clase pública. No hay Lookup en System.Collections.* . La clase de Lookup expuesta a través de LINQ no expone constructores públicamente. No puedes usar la clase excepto a través de ToLookup . También no expone ninguna funcionalidad que no esté ya expuesta a través de ILookup . En este caso particular, diseñaron la interfaz específicamente en torno a este método exacto ( ToLookup ) y la clase Lookup es una clase específicamente diseñada para implementar esa interfaz. Debido a todo esto, prácticamente todos los puntos discutidos sobre la List simplemente no se aplican aquí. Hubiera sido un problema devolver Lookup , no, no realmente. En este caso, realmente no importa mucho de ninguna manera.


La List<T> Retorno List<T> tiene la ventaja de que los métodos de la List<T> que no son parte de IList<T> se usan fácilmente. Hay muchas cosas que puede hacer con una List<T> que no puede hacer con un IList<T> .

Por el contrario, Lookup<TKey, TElement> solo tiene un método disponible que ILookup<TKey, TElement> no tiene ( ApplyResultSelector ), y probablemente no ApplyResultSelector usando eso de todos modos.


La respuesta corta es que, en general, el retorno del tipo más específico disponible está recomendado por las Directrices de diseño de marco autorizadas. (Lo siento, no tengo una cita a la mano, pero recuerdo esto claramente ya que sobresalió en contraste con las directrices de la comunidad de Java que prefieren lo contrario).

Esto tiene sentido para mí. Siempre puede hacer, por ejemplo, IList<int> list = x.ToList() , solo el autor de la biblioteca debe preocuparse de poder soportar el tipo de retorno concreto.

ToLookup<T> es el único en la multitud. Pero perfectamente dentro de las directrices: es el tipo más específico disponible que los autores de la biblioteca están dispuestos a apoyar (como otros han señalado, el tipo concreto Lookup<T> parece ser más un tipo interno no destinado para uso público).


Porque List<T> realmente implementa un rango de interfaces, no solo IList:

public class List<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable{ }

Cada una de esas interfaces define un rango de características que la Lista debe cumplir. Escoger uno en particular, haría inutilizable el grueso de la implementación.

Si desea devolver IList, nada le impide tener su propio contenedor simple:

public static IList<TSource> ToIList<TSource>(this IEnumerable<TSource> source) { if (source == null) throw new ArgumentNullException(source); return source.ToList(); }


Si una función devuelve un objeto inmutable recién construido, la persona que llama generalmente no debería preocuparse por el tipo exacto devuelto, siempre que sea capaz de contener los datos reales que contiene. Por ejemplo, una función que se supone que devuelve un IImmutableMatrix normalmente puede devolver un ImmutableArrayMatrix respaldado por un conjunto privado, pero si todas las celdas tienen ceros, en su lugar podría devolver un ZeroMatrix , respaldado solo por los campos de Width y Height (con un getter eso simplemente devuelve cero todo el tiempo). A la persona que llama no le importaría si recibió una matriz ZeroMatrix o una ZeroMatrix ; ambos tipos permitirían leer todas sus células y garantizar que sus valores nunca cambiarían, y eso es lo que le interesaría a la persona que llama.

Por otro lado, las funciones que devuelven objetos recién construidos que permiten la mutación abierta generalmente deben devolver el tipo preciso que la persona que llama va a esperar. A menos que exista un medio por el cual la persona que llama pueda solicitar diferentes tipos de devolución (por ejemplo, llamando a ToyotaFactory.Build("Corolla") versus ToyotaFactory.Build("Prius") ) no hay ninguna razón para que el tipo de devolución declarada sea otra cosa. Mientras que las fábricas que devuelven objetos inmutables que contienen datos pueden seleccionar un tipo basado en los datos que se van a contener, las fábricas que devuelven tipos mutables libremente no tendrán forma de saber qué datos se pueden poner en ellos. Si las diferentes personas que llaman tendrán diferentes necesidades (por ejemplo, volviendo al ejemplo existente, las necesidades de las personas que llaman se cumplirían con una matriz, mientras que otras no) se les debería dar una opción de métodos de fábrica.

Por cierto, algo así como IEnumerator<T>.GetEnumerator() es un poco un caso especial. El objeto devuelto casi siempre será mutable, pero solo de una manera muy restringida; de hecho, se espera que el objeto devuelto independientemente de su tipo tenga exactamente una parte de estado mutable: su posición en la secuencia de enumeración. Aunque se espera que un IEnumerator<T> sea ​​mutable, las partes de su estado que variarían en las implementaciones de clase derivada no lo son.