c# - usar - ventajas y desventajas de c++
Ventajas/desventajas de diferentes implementaciones para comparar objetos (5)
Esta pregunta involucra 2 implementaciones diferentes de esencialmente el mismo código.
Primero, usar el delegado para crear un método de comparación que se puede usar como un parámetro al ordenar una colección de objetos:
class Foo
{
public static Comparison<Foo> BarComparison = delegate(Foo foo1, Foo foo2)
{
return foo1.Bar.CompareTo(foo2.Bar);
};
}
Uso lo anterior cuando quiero tener una forma de clasificar una colección de objetos Foo de una forma diferente a la que ofrece mi función CompareTo. Por ejemplo:
List<Foo> fooList = new List<Foo>();
fooList.Sort(BarComparison);
En segundo lugar, utilizando IComparer:
public class BarComparer : IComparer<Foo>
{
public int Compare(Foo foo1, Foo foo2)
{
return foo1.Bar.CompareTo(foo2.Bar);
}
}
Uso lo anterior cuando quiero hacer una búsqueda binaria de un objeto Foo en una colección de objetos Foo. Por ejemplo:
BarComparer comparer = new BarComparer();
List<Foo> fooList = new List<Foo>();
Foo foo = new Foo();
int index = fooList.BinarySearch(foo, comparer);
Mis preguntas son:
- ¿Cuáles son las ventajas y desventajas de cada una de estas implementaciones?
- ¿Cuáles son algunas formas más de aprovechar cada una de estas implementaciones?
- ¿Hay alguna forma de combinar estas implementaciones de tal manera que no necesite duplicar el código?
- ¿Puedo lograr una búsqueda binaria y una clasificación alternativa de colección utilizando solo 1 de estas implementaciones?
Ellos realmente abordan diferentes necesidades:
IComparable
es útil para los objetos que están ordenados. Los números reales deben ser comparables, pero los números complejos no pueden, está mal definido.
IComparer
permite definir comparadores reutilizables y bien encapsulados. Esto es especialmente útil si la comparación necesita conocer alguna información adicional. Por ejemplo, es posible que desee comparar fechas y horas de diferentes zonas horarias. Eso puede ser complicado, y se debe usar un comparador separado para este propósito.
Se realiza un método de comparación para operaciones de comparación simples que no son lo suficientemente complicadas como para que la reutilización sea una preocupación, por ejemplo, clasificar una lista de clientes por su nombre. Esta es una operación simple, por lo tanto no necesita datos adicionales. Del mismo modo, esto no es inherente al objeto, porque los objetos no están naturalmente ordenados de ninguna manera.
Por último, hay IEquatable
, lo que podría ser importante si su método Equals
solo puede decidir si dos objetos son iguales o no, pero si no hay una noción de ''más grande'' y ''más pequeño'', por ejemplo, números complejos o vectores en el espacio.
En su caso, la ventaja de tener un IComparer<T>
sobre la Comparision<T>
, es que también puede usarlo para el método de clasificación, por lo que no necesita una versión de delegado de Comparison
.
Otra cosa útil que puedes hacer es implementar una implementación de IComparer<T>
delegada como esta:
public class DelegatedComparer<T> : IComparer<T>
{
Func<T,T,int> _comparision;
public DelegatedComparer(Func<T,T,int> comparision)
{
_comparision = comparision;
}
public int Compare(T a,T b) { return _comparision(a,b); }
}
list.Sort(new DelegatedComparer<Foo>((foo1,foo2)=>foo1.Bar.CompareTo(foo2.Bar));
y una versión más avanzada:
public class PropertyDelegatorComparer<TSource,TProjected> : DelegatedComparer<TSource>
{
PropertyDelegatorComparer(Func<TSource,TProjected> projection)
: base((a,b)=>projection(a).CompareTo(projection(b)))
}
La técnica de delegado es muy corta (las expresiones lambda pueden ser incluso más cortas), por lo que si su objetivo es un código más corto, entonces esta es una ventaja.
Sin embargo, la implementación de IComparer (y su equivalente genérico) hace que su código sea más comprobable: puede agregar algunas pruebas de unidad a su clase / método de comparación.
Además, puede reutilizar la implementación de su comparador al componer dos o más comparadores y combinarlos como un nuevo comparador. La reutilización de códigos con delegados anónimos es más difícil de lograr.
Entonces, para resumir:
Delegados anónimos : código más corto (y quizás más limpio)
Implementación explícita : verificabilidad y reutilización de código.
Probablemente la mayor ventaja de aceptar una Comparison<T>
en lugar de un IComparer<T>
es la capacidad de escribir métodos anónimos. Si tengo, digamos, una List<MyClass>
, donde MyClass
contiene una propiedad de ID
que debería usarse para ordenar, puedo escribir:
myList.Sort((c1, c2) => c1.ID.CompareTo(c2.ID));
Lo que es mucho más conveniente que tener que escribir una IComparer<MyClass>
completa de IComparer<MyClass>
.
No estoy seguro de que aceptar un IComparer<T>
realmente tenga ventajas importantes, excepto la compatibilidad con el código heredado (incluidas las clases de .NET Framework). La propiedad Comparer<T>.Default
solo es realmente útil para los tipos primitivos; Todo lo demás usualmente requiere trabajo extra para codificar.
Para evitar la duplicación de código cuando necesito trabajar con IComparer<T>
, una cosa que generalmente hago es crear un comparador genérico, como este:
public class AnonymousComparer<T> : IComparer<T>
{
private Comparison<T> comparison;
public AnonymousComparer(Comparison<T> comparison)
{
if (comparison == null)
throw new ArgumentNullException("comparison");
this.comparison = comparison;
}
public int Compare(T x, T y)
{
return comparison(x, y);
}
}
Esto permite escribir código como:
myList.BinarySearch(item,
new AnonymousComparer<MyClass>(x.ID.CompareTo(y.ID)));
No es exactamente bonito, pero ahorra tiempo.
Otra clase útil que tengo es esta:
public class PropertyComparer<T, TProp> : IComparer<T>
where TProp : IComparable
{
private Func<T, TProp> func;
public PropertyComparer(Func<T, TProp> func)
{
if (func == null)
throw new ArgumentNullException("func");
this.func = func;
}
public int Compare(T x, T y)
{
TProp px = func(x);
TProp py = func(y);
return px.CompareTo(py);
}
}
El cual puede escribir código diseñado para IComparer<T>
como:
myList.BinarySearch(item, new PropertyComparer<MyClass, int>(c => c.ID));
Realmente no hay ninguna ventaja para ninguna de las opciones en términos de rendimiento. Es realmente una cuestión de conveniencia y mantenimiento del código. Elige la opción que prefieras. Dicho esto, los métodos en cuestión limitan ligeramente sus elecciones.
Puede usar la IComparer<T>
para la List<T>.Sort
, lo que le permitiría no duplicar el código.
Desafortunadamente, BinarySearch no implementa una opción utilizando una Comparison<T>
, por lo que no puede usar un delegado de Comparison<T>
para ese método (al menos no directamente).
Si realmente quisiera usar la Comparison<T>
para ambos, podría hacer una IComparer<T>
genérica de IComparer<T>
que IComparer<T>
un delegado de la Comparison<T>
en su constructor e implementó IComparer<T>
.
public class ComparisonComparer<T> : IComparer<T>
{
private Comparison<T> method;
public ComparisonComparer(Comparison<T> comparison)
{
this.method = comparison;
}
public int Compare(T arg1, T arg2)
{
return method(arg1, arg2);
}
}