2010-06-16 15 views
9

Me he encontrado corriendo a través de un código anterior de 3.5 framework heredado, y encontré algunos puntos donde hay un todo montón de listas y diccionarios que deben actualizarse de manera sincronizada. He determinado que puedo hacer que este proceso sea infinitamente más fácil de utilizar y comprender si los converges en clases de contenedores personalizados de nuevas clases personalizadas. Sin embargo, hay algunos puntos en los que surgió la preocupación de organizar el contenido de estas nuevas clases de contenedores por una propiedad interior específica. Por ejemplo, clasificando por la propiedad de número ID de una clase.Utilidad de la lista <T> .Sort() frente a la lista <T> .OrderBy() para un miembro de una clase de contenedor personalizado

Como las clases de contenedor se basan principalmente en un objeto List genérico, mi primer instinto fue escribir las clases internas con IComparable, y escribir el método CompareTo que compara las propiedades. De esta manera, solo puedo llamar al items.Sort() cuando quiero invocar la clasificación.

Sin embargo, he estado pensando en utilizar items = items.OrderBy(Func) en su lugar. De esta forma, es más flexible si necesito ordenar por cualquier otra propiedad. La legibilidad también es mejor, ya que la propiedad utilizada para la clasificación se enumerará en línea con la llamada de clasificación en lugar de tener que buscar el código IComparable. La implementación general se siente más limpia como resultado.

No me importa la optimización prematura o micro, pero me gusta la consistencia. Encuentro que es mejor seguir con un tipo de implementación para tantos casos como sea apropiado, y usar diferentes implementaciones donde sea necesario. ¿Vale la pena convertir mi código para usar LINQ OrderBy en lugar de usar List.Sort? ¿Es una mejor práctica seguir con la implementación de IComparable para estos contenedores personalizados? ¿Hay alguna ventaja mecánica significativa ofrecida por cualquiera de las dos vías en la que debería ponderar la decisión? ¿O su funcionalidad final es equivalente al punto en que simplemente se convierte en la preferencia del codificador?

Respuesta

18

El punto principal aquí es que List<T>.Sort() hace la clasificación en su lugar. Si su lista está expuesta a código externo, siempre representará el mismo objeto para este código. Esto es importante si la lista se mantiene en un campo por código fuera de la clase contenedora. Si está ordenando con OrderBy(), obtendrá una nueva enumeración cada vez, reemplazando la items anterior. Cualquier lista previamente almacenada no representará el estado actual de su clase.

Teniendo en cuenta el rendimiento, OrderBy tendrá que recorrer la lista completa para ordenar los elementos. Luego, llamará al ToList() para crear la nueva lista de esta enumeración, iterando por la lista por segunda vez. Además, dado que es una enumeración, List usará el algoritmo de duplicación, aumentando su tamaño hasta que todos los elementos puedan encajar en él. En el caso de una lista grande, podría haber bastantes asignaciones y copias de memoria. Esperaría que el rendimiento fuera mucho peor que List<T>.Sort().

Editar: Pequeño referencia:

internal class Program { 

    private static List<int> CreateList(int size) { 

     // use the same seed so that every list has the same elements 
     Random random = new Random(589134554); 

     List<int> list = new List<int>(size); 
     for (int i = 0; i < size; ++i) 
      list.Add(random.Next()); 
     return list; 
    } 

    private static void Benchmark(int size, bool output = true) { 
     List<int> list1 = CreateList(size); 
     List<int> list2 = CreateList(size); 

     Stopwatch stopwatch = Stopwatch.StartNew(); 
     list1.Sort(); 
     stopwatch.Stop(); 
     double elapsedSort = stopwatch.Elapsed.TotalMilliseconds; 
     if (output) 
      Console.WriteLine("List({0}).Sort(): {1}ms (100%)", size, elapsedSort); 

     stopwatch.Restart(); 
     list2.OrderBy(i => i).ToList(); 
     stopwatch.Stop(); 
     double elapsedOrderBy = stopwatch.Elapsed.TotalMilliseconds; 
     if (output) 
      Console.WriteLine("List({0}).OrderBy(): {1}ms ({2:.00%})", size, elapsedOrderBy, elapsedOrderBy/elapsedSort); 

    } 

    internal static void Main() { 

     // ensure linq library is loaded and initialized 
     Benchmark(1000, false); 

     Benchmark(10); 
     Benchmark(100); 
     Benchmark(1000); 
     Benchmark(10000); 
     Benchmark(100000); 
     Benchmark(1000000); 

     Console.ReadKey(); 
    } 
} 

salida (normalizada a list.sort):

List(10).Sort(): 0,0025ms (100%) 
List(10).OrderBy(): 0,0157ms (628,00%) 
List(100).Sort(): 0,0068ms (100%) 
List(100).OrderBy(): 0,0294ms (432,35%) 
List(1000).Sort(): 0,0758ms (100%) 
List(1000).OrderBy(): 0,3107ms (409,89%) 
List(10000).Sort(): 0,8969ms (100%) 
List(10000).OrderBy(): 4,0751ms (454,35%) 
List(100000).Sort(): 10,8541ms (100%) 
List(100000).OrderBy(): 50,3497ms (463,88%) 
List(1000000).Sort(): 124,1001ms (100%) 
List(1000000).OrderBy(): 705,0707ms (568,15%) 
+0

Bueno, el volumen indicado por sus puntos de referencia habla ... volúmenes.Con tasas como esa, incluso puedo incluir 3 tipos para reemplazar ese punto. Había usado 3 OrderBys y obtuve un mejor rendimiento que si hubiera utilizado solo un OrderBy. Sort() lo es! –

+0

Un seguimiento rápido, a juzgar por sus cálculos ... ¿esto también significa que 'items.First()' es significativamente más lento que 'items [0]'? –

+0

No, 'First()' está optimizado para las implementaciones 'IList ' y devolverá 'list [0]'. Espero que el rendimiento sea el mismo. Incluso si estaba usando directamente la enumeración, sigue siendo solo un elemento para iterar, por lo que probablemente sea una micro optimización. –

4

Como se dijo Julien, Sort() será probablemente más rápido, sin embargo, ya que ha mencionado la mejor legibilidad y flexibilidad de OrderBy(), también puede lograr eso con su método Sort() (al menos si la propiedad sobre la que desea basar su orden es comparable)

items = items.OrderBy(x => x.Name).ToList(); 
items.Sort((x,y) => x.Name.CompareTo(y.Name)); // If x.Name is never null 
items.Sort((x,y) => String.Compare(x.Name, y.Name)); // null safe 
+0

Uno de estos días, dejaré de olvidar las pequeñas cosas adicionales como "Puedo especificar la función Ordenar en lugar de confiar en IComparable". Lo cual es genial, porque esto me permite evitar especificar ciertas clases como "comparables" cuando realmente no tienen esa sensación, solo necesitan ser ordenadas en ciertas situaciones. He combinado tu respuesta con los datos de Julien para tener éxito. ¡Muchas gracias! –

+0

Prefiero IComparer a IComparable. De esta forma, puede pasar por diferentes encapsulaciones de algoritmos de clasificación. –

Cuestiones relacionadas