Share via


CA2224: Invalidar Equals al sobrecargar operadores de igualdad

Elemento Valor
RuleId CA2224
Category Microsoft.Usage
Cambio importante Poco problemático

Causa

Un tipo público implementa el operador de igualdad, pero no reemplaza System.Object.Equals.

Descripción de la regla

El operador de igualdad pretende ser una manera sintácticamente cómoda de acceder 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 el 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 bien reemplazar Equals y hacer que los dos métodos devuelvan los mismos valores. Si el operador de igualdad no introduce un comportamiento incoherente, puede corregir la infracción proporcionando una implementación de Equals que llama al método Equals en la clase base.

Cuándo suprimir las advertencias

Una advertencia de esta regla se puede suprimir si el operador de igualdad devuelve el mismo valor que la implementación heredada de Equals. Los ejemplos de este artículo incluyen un tipo en el que se podría suprimir sin problemas una advertencia de esta regla.

Ejemplos de definiciones de igualdad incoherentes

En el ejemplo siguiente se muestra un tipo con definiciones incoherentes de igualdad. BadPoint cambia el significado de la igualdad proporcionando una implementación personalizada del operador de igualdad, pero no invalida Equals para que se comporte de forma idéntica.

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);
        }
    }
}

El código siguiente prueba 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

En el ejemplo siguiente se muestra un tipo que infringe técnicamente esta regla, pero que 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 prueba 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

Ejemplo de clase

En el ejemplo siguiente se muestra una clase (tipo de referencia) que infringe esta regla.

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);        
        }    
    }
}

En el ejemplo siguiente se corrige la infracción mediante el reemplazo de System.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);        
        }    
    }
}

Ejemplo de estructura

En el ejemplo siguiente se muestra una estructura (tipo de valor) que infringe esta regla:

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);        
        }    
    }
}

En el ejemplo siguiente se corrige la infracción mediante el reemplazo de System.ValueType.Equals.

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);        
        }    
    }
}

CA1046: No sobrecargar el operador de igualdad en los tipos de referencia

CA2225: Las sobrecargas del operador tienen alternativas con nombre

CA2226: Los operadores deben tener sobrecargas simétricas

CA2218: Invalidar el método GetHashCode al invalidar el método Equals

CA2231: Sobrecargar el operador equals al invalidar ValueType.Equals