Colección <T> versus Lista <T> ¿qué debe usar en sus interfaces?

162

El código se ve a continuación:

namespace Test
{
    public interface IMyClass
    {
        List<IMyClass> GetList();
    }

    public class MyClass : IMyClass
    {
        public List<IMyClass> GetList()
        {
            return new List<IMyClass>();
        }
    }
}

Cuando ejecuto Code Analysis obtengo la siguiente recomendación.

Advertencia 3 CA1002: Microsoft.Design: Cambie 'List' en 'IMyClass.GetList ()' para usar Collection, ReadOnlyCollection o KeyedCollection

¿Cómo debo solucionar esto y qué es una buena práctica aquí?

bovium
fuente

Respuestas:

234

Para responder a la parte del "por qué" de la pregunta de por qué no List<T>, los motivos son a prueba de futuro y la simplicidad de la API.

A prueba de futuro

List<T>no está diseñado para ser fácilmente extensible subclasificándolo; Está diseñado para ser rápido para implementaciones internas. Notará que los métodos en él no son virtuales y, por lo tanto, no pueden anularse, y no hay ganchos en sus operaciones Add/ Insert/ Remove.

Esto significa que si necesita alterar el comportamiento de la colección en el futuro (por ejemplo, para rechazar objetos nulos que las personas intentan agregar, o para realizar un trabajo adicional cuando esto sucede, como actualizar su estado de clase), entonces debe cambiar el tipo de la colección que regresa a una que puede subclasificar, lo que será un cambio de interfaz de última hora (por supuesto, cambiar la semántica de cosas como no permitir nulo también puede ser un cambio de interfaz, pero cosas como actualizar su estado interno de clase no lo serían).

Así que, ya sea mediante la devolución de una clase que puede ser fácilmente subclases tales como Collection<T>o una interfaz como IList<T>, ICollection<T>oIEnumerable<T> puede cambiar su implementación interna para que sea un tipo de colección diferente para satisfacer sus necesidades, sin romper el código de los consumidores porque aún puede devolverse como el tipo que esperan

Simplicidad API

List<T>contiene muchas operaciones útiles como BinarySearch, Sortetc. Sin embargo, si esta es una colección que está exponiendo, es probable que controle la semántica de la lista, y no los consumidores. Entonces, aunque su clase internamente pueda necesitar estas operaciones, es muy poco probable que los consumidores de su clase quieran (o incluso deberían) llamarlos.

Como tal, al ofrecer una clase o interfaz de colección más simple, reduce la cantidad de miembros que ven los usuarios de su API y les facilita su uso.

Greg Beech
fuente
1
Esta respuesta está muerta. Otra buena lectura sobre el tema se puede encontrar aquí: blogs.msdn.com/fxcop/archive/2006/04/27/…
senfo
77
Veo sus primeros puntos, pero no sé si estoy de acuerdo con su parte de simplicidad de API.
Boris Callens
stackoverflow.com/a/398988/2632991 Aquí también hay una muy buena publicación, sobre cuál es la diferencia entre Colecciones y Listas.
El Mac
2
blogs.msdn.com/fxcop/archive/2006/04/27/… -> Está muerto. ¿Tenemos una información más reciente para esto?
Machado
1
Enlace de trabajo: blogs.msdn.microsoft.com/kcwalina/2005/09/26/…
ofthelit
50

Yo personalmente declararía que devolvería una interfaz en lugar de una colección concreta. Si realmente quieres acceder a la lista, úsala IList<T>. De lo contrario, considere ICollection<T>y IEnumerable<T>.

Jon Skeet
fuente
1
¿IList extendería la interfaz ICollection?
Bovium
8
@ Jon: Sé que esto es viejo, pero ¿puedes comentar lo que Krzysztof dice en blogs.msdn.com/b/kcwalina/archive/2005/09/26/474010.aspx ? Específicamente su comentario, We recommend using Collection<T>, ReadOnlyCollection<T>, or KeyedCollection<TKey,TItem> for outputs and properties and interfaces IEnumerable<T>, ICollection<T>, IList<T> for inputs. CA1002 parece coincidir con los comentarios de Krzysztof. No puedo imaginar por qué se recomendaría una colección concreta en lugar de una interfaz, y por qué la distinción entre entradas / salidas.
Nelson Rothermel
3
@ Nelson: Es raro que desee exigir a las personas que llaman que pasen una lista inmutable, pero es razonable devolver una para que sepan que definitivamente es inmutable. Sin embargo, no estoy seguro de las otras colecciones. Sería bueno tener más detalles.
Jon Skeet
2
No es para un caso específico. Obviamente en general ReadOnlyCollection<T>no tendría sentido para una entrada. Del mismo modo, IList<T>como dice una entrada, "Necesito Sort () o algún otro miembro que tenga una IList" que no tiene sentido para una salida. Pero lo que quise decir fue, ¿por qué se ICollection<T>recomendaría como entrada y Collection<T>como salida? ¿Por qué no usar ICollection<T>como salida también como sugeriste?
Nelson Rothermel
9
Estoy pensando que tiene que ver con la ambigüedad. Collection<T>y ReadOnlyCollection<T>ambos derivan de ICollection<T>(es decir, no hay IReadOnlyCollection<T>). Si devuelve la interfaz, no es obvio cuál es y si se puede modificar. De cualquier forma, gracias por tu aporte. Este fue un buen control de cordura para mí.
Nelson Rothermel el
3

No creo que nadie haya respondido la parte del "por qué" todavía ... así que aquí va. La razón por la cual "usted" debería "usar unCollection<T> lugar de un List<T>es porque si expone un List<T>, entonces cualquiera que tenga acceso a su objeto puede modificar los elementos de la lista. MientrasCollection<T> se supone que indica que está haciendo sus propios métodos "Agregar", "Eliminar", etc.

Es probable que no tenga que preocuparse por ello, porque probablemente esté codificando la interfaz solo para usted (o tal vez para algunos colegas). Aquí hay otro ejemplo que podría tener sentido.

Si tiene una matriz pública, por ejemplo:

public int[] MyIntegers { get; }

Se podría pensar que debido a que solo hay un descriptor de acceso "get" que nadie puede interferir con los valores, pero eso no es cierto. Cualquiera puede cambiar los valores allí dentro de la siguiente manera:

someObject.MyIngegers[3] = 12345;

Personalmente, solo lo usaría List<T>en la mayoría de los casos. Pero si está diseñando una biblioteca de clases que va a entregar a desarrolladores aleatorios, y necesita confiar en el estado de los objetos ... entonces querrá hacer su propia Colección y bloquearla desde allí: )

Timothy Khouri
fuente
"Si devuelve la Lista <T> al código del cliente, nunca podrá recibir notificaciones cuando el código del cliente modifique la colección". - FX COP ... Ver también " msdn.microsoft.com/en-us/library/0fss9skc.aspx " ... wow, parece que no estoy fuera de la base después de todo :)
Timothy Khouri
Wow: años más tarde, veo que el enlace que pegué en el comentario anterior tenía una cita al final, por lo que no funcionó ... msdn.microsoft.com/en-us/library/0fss9skc.aspx
Timothy Khouri
2

Se trata principalmente de abstraer sus propias implementaciones en lugar de exponer el objeto List para que sea manipulado directamente.

No es una buena práctica dejar que otros objetos (o personas) modifiquen el estado de sus objetos directamente. Piense en captadores / establecedores de propiedades.

Colección -> Para colección normal
ReadOnlyCollection -> Para colecciones que no deben modificarse
KeyedCollection -> Cuando desee diccionarios en su lugar.

La forma de solucionarlo depende de lo que desee que haga su clase y del propósito del método GetList (). ¿Puedes elaborar?

chakrit
fuente
1
Pero Collection <T> y KeyedCollection <,> tampoco son de solo lectura.
nawfal
@nawfal ¿Y dónde dije que es?
chakrit
1
No permite que otros objetos (o personas) modifiquen el estado de sus objetos directamente y enumere 3 tipos de colección. Salvar a ReadOnlyCollectionlos otros dos no lo obedece.
nawfal
Eso se refiere a "buenas prácticas". Contexto adecuado por favor. La siguiente lista simplemente establece el requisito básico de tales tipos, ya que el OP quiere entender la advertencia. Luego le pregunto sobre el propósito de GetList()poder ayudarlo más correctamente.
chakrit
1
Ok, bien, entiendo esa parte. Pero aún así, la parte del razonamiento no es sólida según yo. Usted dice que Collection<T>ayuda a abstraer la implementación interna y evita la manipulación directa de la lista interna. ¿Cómo? Collection<T>es simplemente un contenedor, que opera en la misma instancia pasada. Es una colección dinámica destinada a la herencia, nada más (la respuesta de Greg es más relevante aquí).
nawfal
1

En este tipo de casos, generalmente trato de exponer la menor cantidad de implementación necesaria. Si los consumidores no necesitan saber que realmente está utilizando una lista, entonces no necesita devolver una lista. Al regresar cuando Microsoft sugiere una Colección, oculta el hecho de que está utilizando una lista de los consumidores de su clase y los aísla de un cambio interno.

Harald Scheirich
fuente
1

Algo que agregar, aunque ha pasado mucho tiempo desde que se le preguntó.

Cuando su tipo de lista se deriva de, en List<T>lugar de Collection<T>, no puede implementar los métodos virtuales protegidos que Collection<T>implementa. Lo que esto significa es que su tipo derivado no puede responder en caso de que se realicen modificaciones en la lista. Esto se debe a que List<T>asume que usted es consciente cuando agrega o elimina elementos. Ser capaz de responder a las notificaciones es una sobrecarga y, por lo tanto,List<T> lo no lo ofrece.

En los casos en que el código externo tiene acceso a su colección, es posible que no tenga el control de cuándo se agrega o elimina un elemento. Por lo tanto, Collection<T>proporciona una manera de saber cuándo se modificó su lista.

Referencia nula
fuente
0

No veo ningún problema al devolver algo como

this.InternalData.Filter(crteria).ToList();

Si devolví una copia desconectada de los datos internos o el resultado separado de una consulta de datos, puedo regresar de manera segura List<TItem>sin exponer ninguno de los detalles de implementación y permitir el uso de los datos devueltos de la manera conveniente.

Pero esto depende del tipo de consumidor que espero: si se trata de una cuadrícula de datos que prefiero devolver, IEnumerable<TItem> que de todos modos será la lista de elementos copiados en la mayoría de los casos :)

Konstantin Isaev
fuente
-1

Bueno, la clase Colección es realmente una clase envolvente alrededor de otras colecciones para ocultar sus detalles de implementación y otras características. Creo que esto tiene algo que ver con la propiedad de ocultar el patrón de codificación en lenguajes orientados a objetos.

Creo que no debería preocuparse por eso, pero si realmente desea complacer la herramienta de análisis de código, simplemente haga lo siguiente:

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);
Tamas Czinege
fuente
1
Lo siento, fue un error tipográfico. Menat Collection <MyClass>. ¡Tengo muchas ganas de tener en mis manos C # 4 y la covarianza genérica por cierto!
Tamas Czinege
Envolver en un Collection<T>no protege ni a sí mismo ni a la colección subyacente por lo que yo entiendo.
nawfal
Ese código era "complacer a la herramienta de análisis de código". No creo que @TamasCzinege haya dicho en ninguna parte que el uso Collection<T>protegerá inmediatamente su colección subyacente.
chakrit