2012-04-30 9 views
8

Sé que puedo evitar el boxeo al agregar mi propia implementación Equals.¿Por qué no podemos anular Equals() en un tipo de valor sin boxeo?

public struct TwoDoubles 
{ 
    public double m_Re; 
    public double m_Im; 

    public TwoDoubles(double one, double two) 
    { 
     m_Re = one; 
     m_Im = two; 
    } 

    public override bool Equals(object ob) 
    { 
      return Equals((TwoDoubles)ob); 
    } 
    public bool Equals(TwoDoubles ob) 
    { 
     TwoDoubles c = ob; 
     return m_Re == c.m_Re && m_Im == c.m_Im; 
    } 
} 

No puedo llamar esto tanto una sobrecarga como una sobrecarga. Por la magia del tiempo de ejecución, llama correctamente a la implementación correcta Equals() según el tipo de llamante.

¿Por qué no puedo anular y cambiar el tipo de parámetro a TwoDoubles y dejar que el boxeo se produzca con la potencia del tiempo de ejecución según sea necesario? ¿Es porque C# no admite la contravariancia del parámetro (si ese es el motivo, entonces, por qué no es compatible ... parece un pequeño paso desde object o = new TwoDoubles())?

ACTUALIZACIÓN
Aclaración: object es una parte de la jerarquía de herencia de una estructura. ¿Por qué no podemos especificar un tipo más derivado como parámetro para anular una implementación de un tipo menos derivado? Esto nos permitirá escribir:

public override bool Equals(TwoDoubles ob) 
{ 
     TwoDoubles c = ob; 
     return m_Re == c.m_Re && m_Im == c.m_Im;  
} 

que debería ser llamado cuando la variable es una TwoDouble incluso si dicha variable ha sido encasillados en un tipo de objeto.

+1

Esto arrojará una excepción si 'ob' no es' TwoDoubles' en la función de anulación. Y no tiene sentido comprobar el tipo en el otro. – Magnus

+1

@Magnus - corregido. Un descuido ya que estaba corriendo por la puerta –

+4

Solo para que sepa, ya que anula el método 'Igual (objeto)', también debe anular el método 'GetHashCode()' también. –

Respuesta

13

¿Por qué no puedo anular y cambiar el tipo de parámetro a TwoDoubles?

¡Porque eso no sería seguro!

class B 
{ 
    public virtual void M(Animal animal) { ... } 
} 
class D : B 
{ 
    public override void M(Giraffe animal) { ... } 
} 

B b = new D(); 
b.M(new Tiger()); 

¡Y ahora acaba de pasar un tigre a un método que en realidad solo requiere una jirafa!

Lo mismo en su caso. Está anulando un método que toma cualquier objeto con un método que solo puede tomar una estructura; eso no es seguro.

¿Es debido a que C# no es compatible con el tipo de parámetro contravarianza?

No, es porque usted está pidiendo parámetro de tipo covarianza, que no es typesafe.

C# tampoco admite la contravariancia del tipo de parámetro, pero eso no es lo que está pidiendo.

+0

En tu blog, explicas que es la proyección que es covariante/contravariable/invariente, no del tipo. ¿Cuál es la proyección en este caso? http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx –

+2

@ P.Brian.Mackey: Good ¡pregunta! Considere la relación "el método con el tipo de parámetro X puede anular legalmente el método con el tipo de parámetro Y" y la relación "tipo Y es la asignación compatible con el tipo X". Si la primera relación se mantiene cada vez que se cumple la segunda relación, entonces la primera relación es contravariante en el tipo de parámetro. Más precisamente: la proyección es la proyección de "tipo T" a "un método con parámetro tipo T", y es * esta proyección * que es contravariante * con respecto a esas dos relaciones *. –

+1

@ P.Brian.Mackey: Para ser un poco más formal: supongamos que tenemos una relación R1 en un conjunto T; es decir, una función 'R1 (T, T) -> bool'. Supongamos que tenemos una relación R2 en un conjunto M, 'R2 (M, M) -> bool'. Y supongamos que tenemos una proyección P que es 'P (T) -> M'. Si 'R1 (x, y)' es verdadero implica que 'R2 (P (y), P (x))' también es verdadero, entonces P es contravariante. En este caso, R1 es "compatibilidad de tipos de asignación", R2 es "legalidad de un método que anula a otro", y P es "toma un tipo y crea un método que tiene un parámetro de ese tipo". –

2

Puede cambiar el parámetro (sobrecarga) durante Equals, tal y como lo ha hecho, y se realizarán boxeo como sea necesario (es decir, siempre Iguales (objeto) se llama)

Porque todo hereda (o implícitamente a través de boxeo) desde el objeto, no puede evitar que las personas puedan usar Equals (objeto) en su tipo. Sin embargo, al hacerlo, obtienes una llamada en caja.

Si usted está en control del código de llamada, a continuación, utilizar siempre su nueva opción de sobrecarga, es decir Equals(TwoDouble)

Tenga en cuenta, como uno commentor ya se ha dicho, su código es ligeramente incorrecto, debe hacerse lo siguiente:

public override bool Equals(object ob) 
{ 
    if (ob is TwoDoubles) 
    return Equals((TwoDoubles)ob); 
    else 
    return false; 
} 
public bool Equals(TwoDoubles c) 
{ 
    return m_Re == c.m_Re && m_Im == c.m_Im; 
} 

EDITAR como se ha sugerido sabiamente, se debe realizar esta misma tarea, pero mediante el uso de la interfaz IEquatable, en este caso IEquatable<TwoDouble> (nota, sin cambio de código necesaria de lo que hemos hecho, ya que coincide con la firma)

+1

El lanzamiento en el segundo Equals no es realmente necesario. – digEmAll

+6

Sugeriría implementar la interfaz 'IEquatable ' si quiere un método genérico Equals. – Magnus

+0

@digEmAll gracias, copie y pegue el error – payo

2

Si funcionó como sugirió, donde public override bool Equals(TwoDoubles c) { return m_Re == c.m_Re && m_Im == c.m_Im; } era todo lo que era necesario para anular el método Equals(object), ¿qué haría este código?

TwoDoubles td = new TwoDoubles(); 
object o = td; 
bool b = o.Equals(new object()); // Equals(object) overridden with Equals(TwoDouble) 

¿Qué debería pasar en la línea 3?

  • ¿Debería llamar al Equals(TwoDouble)? Si es así, ¿con qué parámetro? ¿Un TwoDouble predeterminado con todos los ceros? Eso violaría las reglas que rodean la igualdad.
  • ¿Debería lanzar una excepción de cast? Seguimos la firma del método correctamente, así que eso no debería suceder.
  • ¿Debería devolver siempre falso? Ahora el compilador tiene que ser consciente de que el método Equal significa, y tendrá que tratarlo de manera diferente a otros métodos.

No hay una buena manera de implementar esto, y rápidamente causa más problemas de los que resuelve.

Cuestiones relacionadas