2009-08-13 15 views
12

Tengo una función que devuelve una cantidad variable de elementos, ¿debo devolver una matriz o una lista? El tamaño de "colección" no cambia una vez devuelto, es decir, para todos los fines, la colección es inmutable. Pensaría simplemente devolver una matriz, pero algunas personas han dicho que no devuelvan matrices de tamaño variable de una función, ya que es "de mala calidad". No estoy seguro por qué?¿Es malo devolver matrices en C#? ¿Debo devolver la lista <T>?

¿Importa que esto tenga que ser compatible con .NET 2.0?

+0

¿Por qué las matrices en C# evil? Sé que en C/C++ son malvados, porque una matriz y un puntero son los mismos – user142350

+2

¿Has visto esto? http://stackoverflow.com/questions/434761/array-versus-listt-when-to-use-which – elan

+2

Odio el término "construcción de programación X es malo", se usa con demasiada frecuencia. No se prefieren las matrices porque pueden ser propensas a errores y proporcionan menos abstracción entre usted y la implementación. Honestamente, en C# desaparecen muchos de los problemas inherentes a las matrices de C++, pero un IList (o alguna otra colección) es una interfaz de nivel superior más agradable. –

Respuesta

20

No es correcto devolver matrices si no es necesario, y especialmente para devolver List<T>.

Generalmente, querrá devolver IEnumerable<T> o IList<T>.

Si su usuario solo va a necesitar pasar por cada elemento, IEnumerable<T> proporcionará esta capacidad. También le permite implementar potencialmente la rutina (ahora o más tarde) utilizando la ejecución diferida.

Si su usuario necesita acceder a los elementos por índice, devuelva IList<T>. Esto proporciona todos los beneficios de las matrices, pero le brinda más flexibilidad en su implementación. Puede implementarlo como una matriz, una lista o alguna otra colección que implemente IList<T>, y no tiene que convertir/copiar a una matriz.

+0

Fui con la lista porque la función ConvertAll fue realmente útil en este caso. – DevDevDev

+0

¿Puedo obtener la longitud de un tipo de IEnumerable? – DevDevDev

+0

La lista va en contra de las pautas de diseño. LINQ proporciona medios para obtener recuentos y conversión, para IEnumerable y IList . –

6

Una opinión Veo muy a menudo una sugerencia para devolver IList<T> o ReadOnlyCollection<T>. Está bien devolverlos si tiene uno de estos disponibles; ambos se pueden asignar directamente a IEnumerable<T> (trabajan directamente con cualquier método LINQ). Una cosa a tener en cuenta es ReadOnlyCollection<T> es un tipo muy ligero que puede envolver cualquier IList<T>.

+1

+1 for 'ReadOnlyCollection' –

0

Si el tamaño de la colección no cambia después de su devolución IEnumerable<T> sería mi elección, ya que la persona que llama podría utilizar los métodos de extensión LINQ con la lista inmediatamente.

0

Es fácil conseguir una matriz desde un IList si una matriz es lo que necesita, pero lo contrario no es cierto. Entonces, es preferible devolver un IList.

1

Como indudablemente ha visto en las respuestas de este hilo, las opiniones sobre este tema son amplias.

En general, mis pensamientos son los siguientes:

Si yo estoy devolviendo una lista cuyo tamaño es constante y no quiero que la persona que llama a poder modificar mis datos (que es el 99% de las veces), Devolveré un ReadOnlyCollection<T>. Esto me da inmutabilidad en el lado del cliente sin tener que doblar (o triplicar, o lo que sea) la huella de memoria de mis datos al crear una nueva lista o una matriz.

Vacilo en decir que "siempre debe devolver IEnumerable o IEnumerable<T>".Si bien esto es ciertamente apropiado en algunos casos (y estos casos no son pocos), la naturaleza liviana de la interfaz IEnumerable limita en gran medida su funcionalidad (ninguna recuperación basada en índices es la más grande), y en muchos casos la fuente subyacente de datos es va a ser una matriz de todos modos, incluso si es un List<T>.

Un peligro adicional de devolver IEnumerable proviene de la práctica perezosa de simplemente devolver la lista interna en el contexto de esa interfaz. Hacer eso lo expone al método de llamada abusando de este atajo volviéndolo al tipo de colección más robusto. Un buen programador defensivo no hará esto.

La huella de memoria más baja proviene de utilizar un ReadOnlyCollection construido a partir de List. A ReadOnlyCollection sigue exponiéndolo al peligro a través del abuso basado en la reflexión y la captura de una referencia a la lista mutable, pero eso es un poco marginal.

+1

" ... lo expone al método de llamada abusando de este atajo devolviéndolo al tipo de colección más robusto. Un buen programador defensivo no hará esto. " Bazofia.La responsabilidad de la biblioteca es crear una interfaz clara para el cliente. La responsabilidad de la biblioteca * no es * anticipar que el cliente basa su código en la parte interna de la biblioteca. Al usar IEnumerable , le está diciendo al cliente que todo lo que garantiza es que obtendrá una lista de los elementos por los que puede pasar. Si vuelve a su tipo interno, es * su * culpa, no suya, si su código rompe con una versión futura. –

+1

@Kyralessa: la programación defensiva consiste en escribir un código que está diseñado de tal manera que elimina (o, de manera más realista, limita) el potencial de abuso por parte de la persona que llama. El casting es un aspecto común y necesario del desarrollo, y minimizar este peligro es temerario. La situación que describo es una parte muy importante de por qué ReadOnlyCollection existe, ya que es una instancia NUEVA que apunta al mismo objeto, en lugar de ver el mismo objeto a través de una interfaz diferente. –

+0

Bueno, siempre puedes obtener lo mejor de ambos mundos: devuelve un ReadOnlyCollection , pero haz que tu método realmente se escriba como IEnumerable . Entonces solo está garantizando la enumerabilidad, pero si lo lanzan a un IList o algo así, aún no les permitirá modificarlo debido a la forma en que IList se implementa en ReadOnlyCollection . –

0

Volver a menos que deba IEnumerable:

  1. acceso Conde - luego regresar IReadOnlyCollection.
  2. modificar elementos y Count es menor ArrayList.Capacity - luego devolver matriz.
  3. agregar nuevos elementos - luego lista de devolución.
  4. algunas microoptimizaciones: hacer referencia. P.ej. foreach sobre matriz es más rápido entonces sobre la lista

BenchmarkDotNet = v0.10.8, OS = Windows 10 Redstone 1 (10.0.14393) Procesador = Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount = 4 Frecuencia = 3233538 Hz, Resolución = 309.2588 ns, Timer = TSC [Host]: Clr 4.0.30319.42000, 64 bits RyuJIT-v4.6.1648.0 Clr: Clr 4.0.30319.42000, 64 bits RyuJIT-v4.6.1648.0 núcleo: núcleo .NET 4.6.25211.01, 64 bits RyuJIT

Method | Job | Runtime |  Mean |  Error | StdDev |  Min |  Max |  Median | Rank | Allocated | 
---------- |----- |-------- |-----------:|-----------:|----------:|-----------:|-----------:|-----------:|-----:|----------:| 
    TestList | Clr |  Clr | 5,153.3 ns | 34.002 ns | 31.806 ns | 5,119.2 ns | 5,213.4 ns | 5,135.9 ns | 3 |  0 B | 
TestArray | Clr |  Clr | 730.1 ns | 6.962 ns | 6.512 ns | 722.4 ns | 743.9 ns | 729.5 ns | 2 |  0 B | 
    TestList | Core | Core | 5,188.4 ns | 102.816 ns | 96.174 ns | 5,070.3 ns | 5,342.6 ns | 5,185.6 ns | 3 |  0 B | 
TestArray | Core | Core | 709.0 ns | 6.126 ns | 5.730 ns | 700.8 ns | 718.6 ns | 708.1 ns | 1 |  0 B | 

Código:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn] 
[ClrJob, CoreJob] 
[HtmlExporter, MarkdownExporter] 
[MemoryDiagnoser] 
public class BenchmarkForEach 
{ 
    List<string> testData = new List<string>(); 
    string[] testArray; 
    public BenchmarkForEach() 
    { 
     for(int i=0;i<1000;i++) 
     { 
      testData.Add(i.ToString()); 
     } 
     testArray = testData.ToArray(); 
    } 
    [Benchmark] 
    public int TestList() 
    { 
     var x = 0; 
     foreach(var i in testData) 
     { 
      x += i.Length; 
     } 
     return x; 
    } 
    [Benchmark] 
    public int TestArray() 
    { 
     var x = 0; 
     foreach (var i in testArray) 
     { 
      x += i.Length; 
     } 
     return x; 
    } 

} 
Cuestiones relacionadas