CA2224: переопределяйте равенство при перегрузке оператора равенства
TypeName |
OverrideEqualsOnOverloadingOperatorEquals |
CheckId |
CA2224 |
Категория |
Microsoft.Usage |
Критическое изменение |
Не критическое |
Причина
Открытый тип реализует оператор равенства, но не переопределяет Object.Equals.
Описание правила
Оператор равенства — это синтаксически удобный способ получения доступа к функциональности метода Equals. При реализации оператора равенства его логика должна совпадать с логикой метода Equals.
Если код нарушает это правило, компилятор C# выдает предупреждение.
Устранение нарушений
Чтобы устранить нарушение этого правила, следует либо удалить реализацию оператора равенства, либо переопределить Equals, чтобы оба метода возвращали одинаковые значения. Если оператор равенства не вызывает неоднородного поведения, можно устранить нарушение, предоставив реализацию Equals, вызывающую метод Equals в базовом классе.
Отключение предупреждений
Можно отключать предупреждения этого правила, если оператор равенства возвращает такое же значение, как и унаследованная реализация Equals. В разделе примера ниже приведен тип, для которого можно безопасно отключать предупреждения этого правила.
Примеры неоднородных определений равенства
Описание
В следующем примере показан тип с несогласованными определениями равенства. BadPoint изменяет значение равенства, предоставляя пользовательскую реализацию оператора равенства, но не переопределяет Equals, так что он ведет себя идентично.
Код
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);
}
}
}
Пример
В следующем коде проверяется поведение 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");
}
}
}
После выполнения примера получается следующий результат.
В следующем примере показан тип, который технически нарушает это правило, но его поведение не является неоднородным.
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));
}
}
}
В следующем коде проверяется поведение 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");
}
}
}
После выполнения примера получается следующий результат.
В следующем примере нарушение устраняется путем переопределения 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);
}
}
}
В следующем примере нарушение устраняется путем переопределения 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);
}
}
}
Пример класса
Описание
В следующем примере демонстрируется класс (ссылочный тип), нарушающий это правило.
Код
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);
}
}
}
Пример структуры
Описание
В следующем примере показана структура (тип значения), нарушающая это правило.
Код
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);
}
}
}
Связанные правила
CA1046: не перегружайте оператор равенства для ссылочных типов
CA2225: для перезагрузок оператора существуют дополнения с именами
CA2226: перегрузки операторов должны быть симметричны
CA2218: переопределяйте GetHashCode при переопределении Equals
CA2231: перегружать равенство операторов следует при перегрузке ValueType.Equals