2011-09-04 11 views
29

¿Debo implementar tanto IComparable como el genérico IComparable<T>? ¿Hay alguna limitación si solo implemento uno de ellos?IComparable e IComparable <T>

+0

Solo necesita escribir el código para 'IComparable '. Obtienes 'IComparable' de forma gratuita al delegar las comparaciones reales a la (s) implementación (es) genérica (s). –

Respuesta

18

Sí, debe implementar ambos.

Si implementa uno, cualquier código que dependa del otro fallará.

Hay un montón de código que usa ya sea IComparable o IComparable<T> pero no ambos, por lo tanto, la implementación de ambos garantiza que su código funcionará con dicho código.

+0

Si controla todo el código, ¿puede determinar que solo necesita implementar la versión genérica? ¿O parte del código de marco aún requiere la versión no genérica? –

+0

@David, algunas clases de marco (por ejemplo, colecciones) pueden basarse en una u otra. –

+5

@David - 'IComparable' ciertamente es usado por algún código de framework. Creo que 'Array.Sort' y' ArrayList.Sort' lo usan. – Oded

4

Mientras IEquatable <T> generalmente no debería ser implementada por clases sin sellar, ya que tal derivación jugaría curiosamente con la herencia a menos que la aplicación simplemente llama Object.equals (en cuyo caso no tendría sentido), la situación contraria surge con la genérico IComparable <T>. La semántica de Object.Equals e IEquatable <T> implica que siempre que se defina IEquatable <T>, su comportamiento debe reflejar el de Object.Equals (además de ser posiblemente más rápido y evitar el boxeo). Dos objetos de tipo DerivedFoo que se comparan como iguales cuando se los considera como tipo DerivedFoo también deben comparar iguales cuando se consideran objetos del tipo Foo, y viceversa. Por otro lado, es completamente posible que dos objetos de tipo DerivedFoo que se clasifican de manera desigual cuando se consideran como tipo DerivedFoo se clasifiquen por igual cuando se los considere como tipo Foo. La única manera de asegurar esto es usar IComparable <T>.

Supongamos, por ejemplo, que uno tiene una clase SchedulerEvent que contiene los campos ScheduledTime (de tipo DateTime) y ScheduledAction (de tipo MethodInvoker). La clase incluye los subtipos SchedulerEventWithMessage (que agrega un campo de mensaje de tipo string) y SchedulerEventWithGong (que agrega un campo GongVolume de tipo Double). La clase SchedulerEvent tiene un orden natural, por ScheduledTime, pero es completamente posible que los eventos que están desordenados uno con respecto al otro sean desiguales. Las clases SchedulerEventWithMessage y SchedulerEventWithGong también tienen un orden natural entre ellos, pero no cuando se comparan con elementos de la clase SchedulerEvent.

Supongamos que uno tiene dos eventos SchedulerEventWithMessage X e Y programados para el mismo tiempo, pero X.Message es "aardvark" e Y.Message es "zymurgy". ((IComparable <SchedulerEvent>) X) .CompareTo (Y) debe informar cero (dado que los eventos tienen tiempos iguales) pero ((IComparable <SchedulerEventWithMessage>) X) .CompareTo (Y) debe devolver un número negativo (ya que "aardvark" tipo antes de "zymurgy"). Si la clase no se comportó de esa manera, sería difícil o imposible ordenar de manera consistente una lista que contenga una mezcla de objetos SchedulerEventWithMessage y SchedulerEventWithGong.

Dicho sea de paso, se podría argumentar que sería útil tener la semántica de IEtabletable <T> comparando objetos solo en la base de los miembros de tipo T, de modo que, p. IEtabletable <SchedulerEvent> comprobará ScheduledTime y ScheduledAction para la igualdad, pero incluso cuando se aplica a un SchedulerEventWithMessage o SchedulerEventWithGong no verificaría las propiedades Message o GongVolume. De hecho, esas serían semánticas útiles para un método de 0tablecimiento < de IE, y yo preferiría esa semántica, pero para un problema: Comparer <T> .Default.GetHashCode (T) siempre llama a la misma función Object.GetHashCode() independientemente de tipo T. Esto limita en gran medida la capacidad de IEtabletable <T> para variar su comportamiento con diferentes tipos T.

+0

Muy interesante respuesta. –

19

Oded tiene razón en que debe implementar ambos porque hay colecciones y otras clases que dependen de una sola de las implementaciones.

Pero hay un truco allí: IComparable <T> no debería arrojar excepciones, mientras que IComparable debería. Al implementar IComparable <T>, usted se encarga de garantizar que todas las instancias de T se puedan comparar entre sí. Esto incluye nulo, también (tratar nulo como más pequeño que todas las instancias no nulas de T y estarás bien).

Sin embargo, general IComparable acepta System.Object y no puede garantizar que todos los objetos imaginables sean comparables contra instancias de T. Por lo tanto, si obtiene una instancia no T pasada a IComparable, simplemente ejecute System.ArgumentException . De lo contrario, enrute la llamada a la implementación de IComparable <T>.

Aquí está el ejemplo:

public class Piano : IComparable<Piano>, IComparable 
{ 
    public int CompareTo(Piano other) { ... } 
    ... 
    public int CompareTo(object obj) 
    { 

     if (obj != null && !(obj is Piano)) 
      throw new ArgumentException("Object must be of type Piano."); 

     return CompareTo(obj as Piano); 

    } 
} 

Este ejemplo es parte de un artículo mucho más largo que contiene un amplio análisis de los efectos secundarios que se debe tener cuidado de la hora de implementar IComparable <T>: How to Implement IComparable<T> Interface in Base and Derived Classes

Cuestiones relacionadas