2011-08-13 11 views
10

Este es un ejemplo de lo que estoy tratando de hacer:¿Cómo puedo mejorar este código: Herencia y IEquatable <>

public class Foo : IEquatable<Foo> 
{ 
    public bool Equals(Foo other) 
    { 
     Type type1 = this.GetType(); 
     Type type2 = other.GetType(); 

     if (type1 != type2) 
      return false; 

     if (type1 == typeof(A)) 
     { 
      A a = (A)this; 
      A b = (A)other; 

      return a.Equals(b); 
     } 
     else if (type1 == typeof(B)) 
     { 
      B c = (B)this; 
      B d = (B)other; 

      return c.Equals(d); 
     } 
     else 
     { 
      throw new Exception("Something is wrong"); 
     } 
    } 
} 

public class A : Foo, IEquatable<A> 
{ 
    public int Number1 { get; set; } 
    public int Number2 { get; set; } 

    public bool Equals(A other) 
    { 
     return this.Number1 == other.Number1 && this.Number2 == other.Number2; 
    } 
} 

public class B : Foo, IEquatable<B> 
{ 
    public int Number1 { get; set; } 
    public int Number2 { get; set; } 
    public int Number3 { get; set; } 

    public bool Equals(B other) 
    { 
     return this.Number1 == other.Number1 && this.Number2 == other.Number2 && this.Number3 == other.Number3; 
    } 
} 

Pero como se puede ver arriba, tendría que utilizar muchos condicionales 'si' para identificar el tipo real. El problema es que tengo que usar la clase base. Por ejemplo:

A a = new A(); 
Foo foo = a; 

foo.Equals(another); 
+0

¿Por qué necesita 'IEquatable ' en primer lugar? No está agregando ningún valor aquí. Solo asegúrese de que los subtipos (con sensatez) anulen 'object.GetHashCode' y' object.Equals (object) 'también, además de la implementación existente de 'IEquatable '. Luego obtendrá el envío de métodos virtuales de forma gratuita, y funcionará en muchas más situaciones. – Ani

+0

@Ani ¿Me mostrarías un ejemplo? –

+0

Hmm. Estas clases parecen extrañas. ¿Cuál es el propósito de Foo ya que no tiene propiedades? Además, si B es solo A con un Número más, ¿por qué B no hereda de A y simplemente agrega Number3 en lugar de Foo? – alun

Respuesta

1

probar este pedazo de código:

public class Foo : IEquatable<Foo> 
{ 
    public virtual bool Equals(Foo other) 
    { 
     return true; 
    } 
} 

public class A : Foo,IEquatable<A> 
{ 
    public int Number1 { get; set; } 
    public int Number2 { get; set; } 

    public override bool Equals(Foo other) 
    { 
     if (other.GetType() == typeof(A)) 
     { 
      return Equals((A)other);     
     } 
     throw new InvalidOperationException("Object is not of type A"); 
    } 
    public bool Equals(A other) 
    { 
     return this.Number1 == other.Number1 && this.Number2 == other.Number2; 
    } 
} 

public class B : Foo,IEquatable<B> 
{ 
    public int Number1 { get; set; } 
    public int Number2 { get; set; } 
    public int Number3 { get; set; } 

    public override bool Equals(Foo other) 
    { 
     if (other.GetType() == typeof(B)) 
     { 
      return Equals((B)other); 

     } 
     throw new InvalidOperationException("Object is not of type B"); 
    } 
    public bool Equals(B other) 
    { 
     return this.Number1 == other.Number1 && this.Number2 == other.Number2 && this.Number3 == other.Number3; 
    } 
} 

Nota: Puede utilizar la funcionalidad de aserción de hacer verificación de tipos.

+0

Se ve bien, al principio estaba pensando en establecer la clase Foo como abstracto. Me gusta de esta manera, parece extraño virtual en la clase Foo, ¿es posible reemplazar la clase Foo a la interfaz? –

+0

Adelante, la elección es suya, acabo de proporcionarle la solución .... – RockWorld

3

Como respuesta directa a su pregunta, parece implementar IEquatable<Foo> al aplazar siempre la implementación de la subclase (concreta) IEquatable<self>. Esto sería algo como:

(código incorrecto, solamente para la explicación)

// You need to specify what you want when this method is called on a 
// vanilla Foo object. I assume here that Foo is abstract. If not, please 
// specify desired behaviour. 
public bool Equals(Foo other) 
{ 
    if (other == null || other.GetType() != GetType()) 
     return false; 

    // You can cache this MethodInfo.. 
    var equalsMethod = typeof(IEquatable<>).MakeGenericType(GetType()) 
              .GetMethod("Equals"); 

    return (bool)equalsMethod.Invoke(this, new object[] { other }); 
} 

Pero realmente No está claro por qué necesita las comparaciones de igualdad de ir siempre "a través de" la base de la clase de IEquatable<self> implementación.

El marco ya tiene el método virtual Equals que dará como resultado el envío de llamadas de igualdad al método apropiado. Además, EqualityComparar<T>.Default (que es utilizado por la mayoría de los tipos de colección para realizar verificaciones de igualdad) ya tiene tiene la inteligencia para elegir IEquatable<self>.Equals(self) o object.Equals(object) según corresponda.

Tratar de crear una implementación de la igualdad en la clase base que simplemente reenvía la solicitud es la adición de ningún valor a nada, por lo que yo puedo ver.

Sin más explicaciones sobre por qué necesita la implementación de la clase base IEquatable<>, le recomiendo que implemente la igualdad correctamente en cada tipo. Por ejemplo:

public class A : Foo, IEquatable<A> 
{ 
    public int Number1 { get; set; } 
    public int Number2 { get; set; } 

    public bool Equals(A other) 
    { 
     return other != null 
      && Number1 == other.Number1 
      && Number2 == other.Number2; 
    } 

    public override bool Equals(object obj) 
    { 
     return Equals(obj as A); 
    } 

    public override int GetHashCode() 
    { 
     return Number1^Number2; 
    } 
} 
+0

Creo que se perdió una llamada a Igual genérico en su no genérico Igual – alun

+0

@alún: ¡Vaya, gracias! Fijo. – Ani

0

Una opción es mover las propiedades Number1 y Number2 a la clase base, y sólo comparar el miembro añadido a la subclase en los métodos de la igualdad de las subclases.

class Foo 
{ 
    // move the common properties to the base class 
    public int Number1 { get; set; } 
    public int Number2 { get; set; } 

    public override bool Equals(object obj) 
    { 
     Foo objfoo = obj as Foo; 
     return 
      objfoo != null 
      // require objects being compared to be of 
      // the same derived type (optionally) 
      && this.GetType() == obj.GetType() 
      && objfoo.Number1 == this.Number1 
      && objfoo.Number2 == this.Number2; 
    } 
    public override int GetHashCode() 
    { 
     // xor the hash codes of the elements used to evaluate 
     // equality 
     return Number1.GetHashCode()^Number2.GetHashCode(); 
    } 
} 

class A : Foo, IEquatable<A> 
{ 
    // A has no properties Foo does not. Simply implement 
    // IEquatable<A> 

    public bool Equals(A other) 
    { 
     return this.Equals(other); 
    } 

    // can optionally override Equals(object) and GetHashCode() 
    // to call base methods here 
} 

class B : Foo, IEquatable<B> 
{ 
    // Add property Number3 to B 
    public int Number3 { get; set; } 
    public bool Equals(B other) 
    { 
     // base.Equals(other) evaluates Number1 and Number2 
     return base.Equals(other) 
      && this.Number3 == other.Number3; 
    } 
    public override int GetHashCode() 
    { 
     // include Number3 in the hashcode, since it is used 
     // to evaluate equality 
     return base.GetHashCode()^Number3.GetHashCode(); 
    } 
    public override bool Equals(object obj) 
    { 
     return this.Equals(obj as B); 
    } 
} 
0

Creo que las clases derivadas no se deben manejar en las clases base. Por lo general, "Foo" no sabrá nada sobre A y B.

Todavía es posible hacer que la implementación básica de IEquatable sea virtual, permitiendo que A y B lo anulen y realicen sus comprobaciones de igualdad específicas, incluso si tanto la comprobación de igualdad como la instancia controlada están disponibles solo como "Foo" u "Objeto".

Eso trataría .Equals (Foo obj) como una forma más específica de Object.Equals (Object obj).

Cuestiones relacionadas