Reemplazar Equals al sobrecargar el operador de igualdad
Actualización: noviembre 2007
Nombre de tipo |
OverrideEqualsOnOverloadingOperatorEquals |
Identificador de comprobación |
CA2224 |
Categoría |
Microsoft.Usage |
Cambio problemático |
No problemático |
Motivo
Un tipo público implementa el operador de igualdad, pero no reemplaza Object.Equals.
Descripción de la regla
El operador de igualdad está pensado para que desde un punto de vista sintáctico sea un medio práctico de tener acceso a la funcionalidad del método Equals. Si implementa el operador de igualdad, su lógica debe ser idéntica a la de Equals.
El compilador de C# emite una advertencia si su código infringe esta regla.
Cómo corregir infracciones
Para corregir una infracción de esta regla, debe quitar la implementación del operador de igualdad o reemplazar Equals, y que los dos métodos devuelvan los mismos valores. Si el operador de igualdad no produce un comportamiento incoherente, puede corregir la infracción proporcionando una implementación de Equals que llame al método Equals de la clase base.
Cuándo suprimir advertencias
Es seguro suprimir una advertencia de esta regla si el operador de igualdad devuelve el mismo valor que la implementación heredada de Equals. La sección Ejemplo incluye un tipo que puede suprimir una advertencia de esta regla sin ningún riesgo.
Ejemplos de definiciones de igualdad incoherentes
Descripción
El siguiente ejemplo muestra un tipo con definiciones de igualdad incoherentes. BadPoint modifica el significado de igualdad proporcionando una implementación personalizada del operador de igualdad, pero no reemplaza Equals de modo que su comportamiento es idéntico.
Código
using System;
namespace UsageLibrary
{
public class BadPoint
{
private int x,y, id;
private static int NextId;
static BadPoint()
{
NextId = -1;
}
public BadPoint(int x, int y)
{
this.x = x;
this.y = y;
id = ++(BadPoint.NextId);
}
public override string ToString()
{
return String.Format("([{0}] {1},{2})",id,x,y);
}
public int X {get {return x;}}
public int Y {get {return x;}}
public int Id {get {return id;}}
public override int GetHashCode()
{
return id;
}
// Violates rule: OverrideEqualsOnOverridingOperatorEquals.
// BadPoint redefines the equality operator to ignore the id value.
// This is different from how the inherited implementation of
// System.Object.Equals behaves for value types.
// It is not safe to exclude the violation for this type.
public static bool operator== (BadPoint p1, BadPoint p2)
{
return ((p1.x == p2.x) && (p1.y == p2.y));
}
// The C# compiler and rule OperatorsShouldHaveSymmetricalOverloads require this.
public static bool operator!= (BadPoint p1, BadPoint p2)
{
return !(p1 == p2);
}
}
}
Ejemplo
El código siguiente comprueba el comportamiento de BadPoint.
using System;
namespace UsageLibrary
{
public class TestBadPoint
{
public static void Main()
{
BadPoint a = new BadPoint(1,1);
BadPoint b = new BadPoint(2,2);
BadPoint a1 = a;
BadPoint bcopy = new BadPoint(2,2);
Console.WriteLine("a = {0} and b = {1} are equal? {2}", a, b, a.Equals(b)? "Yes":"No");
Console.WriteLine("a == b ? {0}", a == b ? "Yes":"No");
Console.WriteLine("a1 and a are equal? {0}", a1.Equals(a)? "Yes":"No");
Console.WriteLine("a1 == a ? {0}", a1 == a ? "Yes":"No");
// This test demonstrates the inconsistent behavior of == and Object.Equals.
Console.WriteLine("b and bcopy are equal ? {0}", bcopy.Equals(b)? "Yes":"No");
Console.WriteLine("b == bcopy ? {0}", b == bcopy ? "Yes":"No");
}
}
}
Este ejemplo produce el siguiente resultado.
a = ([0] 1,1) and b = ([1] 2,2) are equal? No
a == b ? No
a1 and a are equal? Yes
a1 == a ? Yes
b and bcopy are equal ? No
b == bcopy ? Yes
El ejemplo siguiente muestra un tipo que técnicamente infringe esta regla, pero no se comporta de forma incoherente.
using System;
namespace UsageLibrary
{
public struct GoodPoint
{
private int x,y;
public GoodPoint(int x, int y)
{
this.x = x;
this.y = y;
}
public override string ToString()
{
return String.Format("({0},{1})",x,y);
}
public int X {get {return x;}}
public int Y {get {return x;}}
// Violates rule: OverrideEqualsOnOverridingOperatorEquals,
// but does not change the meaning of equality;
// the violation can be excluded.
public static bool operator== (GoodPoint px, GoodPoint py)
{
return px.Equals(py);
}
// The C# compiler and rule OperatorsShouldHaveSymmetricalOverloads require this.
public static bool operator!= (GoodPoint px, GoodPoint py)
{
return !(px.Equals(py));
}
}
}
El código siguiente comprueba el comportamiento de GoodPoint.
using System;
namespace UsageLibrary
{
public class TestGoodPoint
{
public static void Main()
{
GoodPoint a = new GoodPoint(1,1);
GoodPoint b = new GoodPoint(2,2);
GoodPoint a1 = a;
GoodPoint bcopy = new GoodPoint(2,2);
Console.WriteLine("a = {0} and b = {1} are equal? {2}", a, b, a.Equals(b)? "Yes":"No");
Console.WriteLine("a == b ? {0}", a == b ? "Yes":"No");
Console.WriteLine("a1 and a are equal? {0}", a1.Equals(a)? "Yes":"No");
Console.WriteLine("a1 == a ? {0}", a1 == a ? "Yes":"No");
// This test demonstrates the consistent behavior of == and Object.Equals.
Console.WriteLine("b and bcopy are equal ? {0}", bcopy.Equals(b)? "Yes":"No");
Console.WriteLine("b == bcopy ? {0}", b == bcopy ? "Yes":"No");
}
}
}
Este ejemplo produce el siguiente resultado.
a = (1,1) and b = (2,2) are equal? No
a == b ? No
a1 and a are equal? Yes
a1 == a ? Yes
b and bcopy are equal ? Yes
b == bcopy ? Yes
El ejemplo siguiente corrige la infracción invalidando Object.Equals.
using System;
namespace Samples
{
public class Point
{
private readonly int _X;
private readonly int _Y;
public Point(int x, int y)
{
_X = x;
_Y = y;
}
public int X
{
get { return _X; }
}
public int Y
{
get { return _Y; }
}
public override int GetHashCode()
{
return _X ^ _Y;
}
public override bool Equals(object obj)
{
if (obj == null)
return false;
if (GetType() != obj.GetType())
return false;
Point point = (Point)obj;
if (_X != point.X)
return false;
return _Y == point.Y;
}
public static bool operator ==(Point point1, Point point2)
{
return Object.Equals(point1, point2);
}
public static bool operator !=(Point point1, Point point2)
{
return !Object.Equals(point1, point2);
}
}
}
El ejemplo siguiente corrige la infracción invalidando ValueTypeEquals().
using System;
namespace Samples
{
public struct Point : IEquatable<Point>
{
private readonly int _X;
private readonly int _Y;
public Point(int x, int y)
{
_X = x;
_Y = y;
}
public int X
{
get { return _X; }
}
public int Y
{
get { return _Y; }
}
public override int GetHashCode()
{
return _X ^ _Y;
}
public override bool Equals(object obj)
{
if (!(obj is Point))
return false;
return Equals((Point)obj);
}
public bool Equals(Point other)
{
if (_X != other._X)
return false;
return _Y == other._Y;
}
public static bool operator ==(Point point1, Point point2)
{
return point1.Equals(point2);
}
public static bool operator !=(Point point1, Point point2)
{
return !point1.Equals(point2);
}
}
}
Ejemplo de clase
Descripción
En el siguiente ejemplo se muestra una clase (tipo de referencia) que infringe esta regla.
Código
using System;
namespace Samples
{
// Violates this rule
public class Point
{
private readonly int _X;
private readonly int _Y;
public Point(int x, int y)
{
_X = x;
_Y = y;
}
public int X
{
get { return _X; }
}
public int Y
{
get { return _Y; }
}
public override int GetHashCode()
{
return _X ^ _Y;
}
public static bool operator ==(Point point1, Point point2)
{
if (point1 == null || point2 == null)
return false;
if (point1.GetType() != point2.GetType())
return false;
if (point1._X != point2._X)
return false;
return point1._Y == point2._Y;
}
public static bool operator !=(Point point1, Point point2)
{
return !(point1 == point2);
}
}
}
Ejemplo de estructura
Descripción
En el siguiente ejemplo se muestra una estructura (tipo de valor) que infringe esta regla.
Código
using System;
namespace Samples
{
// Violates this rule
public struct Point
{
private readonly int _X;
private readonly int _Y;
public Point(int x, int y)
{
_X = x;
_Y = y;
}
public int X
{
get { return _X; }
}
public int Y
{
get { return _Y; }
}
public override int GetHashCode()
{
return _X ^ _Y;
}
public static bool operator ==(Point point1, Point point2)
{
if (point1._X != point2._X)
return false;
return point1._Y == point2._Y;
}
public static bool operator !=(Point point1, Point point2)
{
return !(point1 == point2);
}
}
}
Reglas relacionadas
No sobrecargar el operador Equals en tipos de referencia
Las sobrecargas del operador tienen alternativas con nombre
Los operadores deben tener sobrecargar simétricas
Reemplazar GetHashCode en el método reemplazable Equals
Sobrecargar el operador de igualdad al invalidar ValueType.Equals