Condividi tramite


CA2224: Eseguire l'override di Equals all'override dell'operatore

TypeName

OverrideEqualsOnOverloadingOperatorEquals

CheckId

CA2224

Category

Microsoft.Usage

Breaking Change

Non sostanziale

Causa

Un tipo pubblico implementa l'operatore di uguaglianza, ma non esegue l'override di Object.Equals.

Descrizione della regola

L'operatore di uguaglianza costituisce un modo pratico dal punto di vista sintattico per accedere alla funzionalità del metodo Equals. Se si implementa l'operatore di uguaglianza, la relativa logica deve essere identica a quella di Equals.

Il compilatore C# emette un avviso se il codice viola questa regola.

Come correggere le violazioni

Per correggere una violazione di questa regola, è necessario rimuovere l'implementazione dell'operatore di uguaglianza oppure eseguire l'override di Equals affinché i due metodi restituiscano gli stessi valori. Se l'operatore di uguaglianza non introduce un comportamento incoerente, è possibile correggere la violazione fornendo un'implementazione di Equals che chiama il metodo Equals nella classe base.

Esclusione di avvisi

L'esclusione di un avviso da questa regola è sicura se l'operatore di uguaglianza restituisce lo stesso valore dell'implementazione ereditata di Equals. Nella sezione relativa agli esempi è incluso un tipo che consente l'esclusione sicura di un avviso da questa regola.

Esempi di definizioni di uguaglianza incoerenti

Oggetto di descrizione

Nell'esempio seguente viene mostrato un tipo con definizioni incoerenti di uguaglianza. BadPoint modifica il significato di uguaglianza fornendo un'implementazione personalizzata dell'operatore di uguaglianza, ma non esegue l'override di Equals in modo da presentare un comportamento identico.

Codice

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

Esempio

Nel codice riportato di seguito viene verificato il comportamento di 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");
        }
    }
}

Questo esempio produce l'output che segue.

                

Nell'esempio riportato di seguito viene illustrato un tipo che viola tecnicamente questa regola, ma non presenta un comportamento incoerente.

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

Nel codice riportato di seguito viene verificato il comportamento di 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");
        }
    }
}

Questo esempio produce l'output che segue.

                

Nell'esempio seguente viene corretta la violazione eseguendo l'override di 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);        
        }    
    }
}

Nell'esempio seguente viene corretta la violazione eseguendo l'override di 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);        
        }    
    }
}

Esempio di classe

Oggetto di descrizione

Nell'esempio riportato di seguito viene illustrata una classe (tipo di riferimento) che viola questa regola.

Codice

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

Esempio di struttura

Oggetto di descrizione

Nell'esempio riportato di seguito viene mostrata una struttura (tipo di valore) che viola questa regola.

Codice

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

Regole correlate

CA1046: Non eseguire l'overload dell'operatore "uguale a" per i tipi di riferimento

CA2225: Gli overload degli operatori hanno alternative con nome

CA2226: Gli operatori devono avere overload simmetrici

CA2218: Eseguire l'override di GetHashCode all'override di Equals

CA2231: Eseguire l'overload dell'operatore "uguale a" all'override di ValueType.Equals