Jak: Definiowanie równość wartości dla typu (Podręcznik programowania C#)
Podczas definiowania klasy lub struktury zdecydować czy sensowne do utworzyć niestandardowych definicji równości wartość (lub równoważności) dla danego typu.Zazwyczaj zaimplementowaniem równość wartości mają zostać dodane do kolekcji jakiegoś oczekuje obiektów typu lub gdy ich głównym celem jest przechowywać zestaw pól lub właściwości.Definicja, równość wartości można oprzeć na porównanie pola i właściwości typu lub można oprzeć definicję na podzbiorze.Jednak w każdym przypadku i zarówno klas, jak i strukturach implementacji należy postępować zgodnie z pięciu gwarancje równoważności:
x.Equals(x) zwraca true. jest to tak zwane zwrotnej właściwość.
x.Equals(y) zwraca taką samą wartość jak y.Equals(x).Jest to nazywane symetrycznego właściwość.
Jeśli (x.Equals(y) & & y.Equals(z)) returns true, then x.Equals(z) returns true.Jest to nazywane przechodnie właściwość.
Kolejne wywołania x.Equals(y) return taką samą wartość jak długo obiekty odwołuje się x i y nie są modyfikowane.
x.Equals(null) returns false.Jednakże null.Equals(NULL) zgłasza wyjątek; to nie stosuj numer reguły dwóch powyższych.
Wszelkie struct definiujących już została domyślna implementacja równości wartość, która dziedziczy z System.ValueType zastąpić z Object.Equals(Object) metoda. Ta implementacja używa odbicie badania publicznych i niepublicznych pól i właściwości typu.Chociaż ta implementacja produkuje poprawnych wyników, jest stosunkowo powolne w porównaniu do niestandardowej implementacji, napisany specjalnie dla danego typu.
Szczegóły dotyczące implementacji dla wartości równości są różne dla klas i strukturach.Jednakże zarówno klas, jak i strukturach wymagają te same podstawowe kroki wykonywania równości:
Zastąpić wirtualnegoObject.Equals(Object)metoda. W większości przypadków implementacji bool Equals( object obj ) po prostu należy zadzwonić do określonego typu Equals metoda wdrażania jest System.IEquatable<T> interfejs. (Zobacz krok 2).
Wdrożenie System.IEquatable<T> interfejs dostarczając określonego typu Equals metoda. Jest to, gdzie wykonywane jest porównanie rzeczywistych równoważności.Na przykład można zdecydować zdefiniować równości poprzez porównanie tylko jednego lub dwóch pól w sieci typu.Nie generują wyjątki od Equals.Dla klas tylko: ta metoda powinna zbadać tylko te pola, które są zadeklarowane w klasie.Powinna wywołać base.Equals do zbadania pól, które znajdują się w klasie podstawowej.(Nie należy tego robić, jeśli typ dziedziczy bezpośrednio z Object, ponieważ Object wykonania Object.Equals(Object) wykonuje sprawdzanie równości odwołanie.)
Opcjonalne, ale zalecane: przeciążenie == i ! = podmiotów gospodarczych.
Zastąpić Object.GetHashCode tak, że dwa obiekty, które mają wartość równości produkuje ten sam wartość skrótu.
Opcjonalnie: Aby obsługiwać definicje "większy niż" lub "mniejszy niż", zaimplementować IComparable<T> interfejs użytkownika typu, a także przeładowanie < = i > = podmiotów gospodarczych.
W pierwszym przykładzie, który następuje przedstawiono Implementacja klasy.Drugi przykład przedstawia wykonania struct.
Przykład
Poniższy przykład pokazuje, jak zaimplementować wartość równości w klasie (typ referencyjny).
namespace ValueEquality
{
using System;
class TwoDPoint : IEquatable<TwoDPoint>
{
// Readonly auto-implemented properties.
public int X { get; private set; }
public int Y { get; private set; }
// Set the properties in the constructor.
public TwoDPoint(int x, int y)
{
if ((x < 1) || (x > 2000) || (y < 1) || (y > 2000))
throw new System.ArgumentException("Point must be in range 1 - 2000");
this.X = x;
this.Y = y;
}
public override bool Equals(object obj)
{
return this.Equals(obj as TwoDPoint);
}
public bool Equals(TwoDPoint p)
{
// If parameter is null, return false.
if (Object.ReferenceEquals(p, null))
{
return false;
}
// Optimization for a common success case.
if (Object.ReferenceEquals(this, p))
{
return true;
}
// If run-time types are not exactly the same, return false.
if (this.GetType() != p.GetType())
return false;
// Return true if the fields match.
// Note that the base class is not invoked because it is
// System.Object, which defines Equals as reference equality.
return (X == p.X) && (Y == p.Y);
}
public override int GetHashCode()
{
return X * 0x00010000 + Y;
}
public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
{
// Check for null on left side.
if (Object.ReferenceEquals(lhs, null))
{
if (Object.ReferenceEquals(rhs, null))
{
// null == null = true.
return true;
}
// Only the left side is null.
return false;
}
// Equals handles case of null on right side.
return lhs.Equals(rhs);
}
public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)
{
return !(lhs == rhs);
}
}
// For the sake of simplicity, assume a ThreeDPoint IS a TwoDPoint.
class ThreeDPoint : TwoDPoint, IEquatable<ThreeDPoint>
{
public int Z { get; private set; }
public ThreeDPoint(int x, int y, int z)
: base(x, y)
{
if ((z < 1) || (z > 2000))
throw new System.ArgumentException("Point must be in range 1 - 2000");
this.Z = z;
}
public override bool Equals(object obj)
{
return this.Equals(obj as ThreeDPoint);
}
public bool Equals(ThreeDPoint p)
{
// If parameter is null, return false.
if (Object.ReferenceEquals(p, null))
{
return false;
}
// Optimization for a common success case.
if (Object.ReferenceEquals(this, p))
{
return true;
}
// Check properties that this class declares.
if (Z == p.Z)
{
// Let base class check its own fields
// and do the run-time type comparison.
return base.Equals((TwoDPoint)p);
}
else
return false;
}
public override int GetHashCode()
{
return (X * 0x100000) + (Y * 0x1000) + Z;
}
public static bool operator ==(ThreeDPoint lhs, ThreeDPoint rhs)
{
// Check for null.
if (Object.ReferenceEquals(lhs, null))
{
if (Object.ReferenceEquals(rhs, null))
{
// null == null = true.
return true;
}
// Only the left side is null.
return false;
}
// Equals handles the case of null on right side.
return lhs.Equals(rhs);
}
public static bool operator !=(ThreeDPoint lhs, ThreeDPoint rhs)
{
return !(lhs == rhs);
}
}
class Program
{
static void Main(string[] args)
{
ThreeDPoint pointA = new ThreeDPoint(3, 4, 5);
ThreeDPoint pointB = new ThreeDPoint(3, 4, 5);
ThreeDPoint pointC = null;
int i = 5;
Console.WriteLine("pointA.Equals(pointB) = {0}", pointA.Equals(pointB));
Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
Console.WriteLine("null comparison = {0}", pointA.Equals(pointC));
Console.WriteLine("Compare to some other type = {0}", pointA.Equals(i));
TwoDPoint pointD = null;
TwoDPoint pointE = null;
Console.WriteLine("Two null TwoDPoints are equal: {0}", pointD == pointE);
pointE = new TwoDPoint(3, 4);
Console.WriteLine("(pointE == pointA) = {0}", pointE == pointA);
Console.WriteLine("(pointA == pointE) = {0}", pointA == pointE);
Console.WriteLine("(pointA != pointE) = {0}", pointA != pointE);
System.Collections.ArrayList list = new System.Collections.ArrayList();
list.Add(new ThreeDPoint(3, 4, 5));
Console.WriteLine("pointE.Equals(list[0]): {0}", pointE.Equals(list[0]));
// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
/* Output:
pointA.Equals(pointB) = True
pointA == pointB = True
null comparison = False
Compare to some other type = False
Two null TwoDPoints are equal: True
(pointE == pointA) = False
(pointA == pointE) = False
(pointA != pointE) = True
pointE.Equals(list[0]): False
*/
}
Na klasy (typy odwołań) Domyślna implementacja obu Object.Equals(Object) metody wykonuje porównanie równości odniesienia nie wyboru równość wartości.Gdy Realizator zastępuje wirtualna metoda, celem jest nadaj wartość równości semantyka.
== i != operatorów może być używany z klas, nawet jeśli klasa nie przeładowanie je.Jednak zachowanie domyślne jest sprawdzać równości odniesienia.W klasie Jeśli się przeładowanie Equals metodapowinna się przeładowanie == i != operatorów, ale nie jest wymagane.
Poniższy przykład pokazuje, jak zaimplementować wartość równości w struct (typ wartości):
struct TwoDPoint : IEquatable<TwoDPoint>
{
// Read/write auto-implemented properties.
public int X { get; private set; }
public int Y { get; private set; }
public TwoDPoint(int x, int y)
: this()
{
X = x;
Y = x;
}
public override bool Equals(object obj)
{
if (obj is TwoDPoint)
{
return this.Equals((TwoDPoint)obj);
}
return false;
}
public bool Equals(TwoDPoint p)
{
return (X == p.X) && (Y == p.Y);
}
public override int GetHashCode()
{
return X ^ Y;
}
public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
{
return lhs.Equals(rhs);
}
public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)
{
return !(lhs.Equals(rhs));
}
}
class Program
{
static void Main(string[] args)
{
TwoDPoint pointA = new TwoDPoint(3, 4);
TwoDPoint pointB = new TwoDPoint(3, 4);
int i = 5;
// Compare using virtual Equals, static Equals, and == and != operators.
// True:
Console.WriteLine("pointA.Equals(pointB) = {0}", pointA.Equals(pointB));
// True:
Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
// True:
Console.WriteLine("Object.Equals(pointA, pointB) = {0}", Object.Equals(pointA, pointB));
// False:
Console.WriteLine("pointA.Equals(null) = {0}", pointA.Equals(null));
// False:
Console.WriteLine("(pointA == null) = {0}", pointA == null);
// True:
Console.WriteLine("(pointA != null) = {0}", pointA != null);
// False:
Console.WriteLine("pointA.Equals(i) = {0}", pointA.Equals(i));
// CS0019:
// Console.WriteLine("pointA == i = {0}", pointA == i);
// Compare unboxed to boxed.
System.Collections.ArrayList list = new System.Collections.ArrayList();
list.Add(new TwoDPoint(3, 4));
// True:
Console.WriteLine("pointE.Equals(list[0]): {0}", pointA.Equals(list[0]));
// Compare nullable to nullable and to non-nullable.
TwoDPoint? pointC = null;
TwoDPoint? pointD = null;
// False:
Console.WriteLine("pointA == (pointC = null) = {0}", pointA == pointC);
// True:
Console.WriteLine("pointC == pointD = {0}", pointC == pointD);
TwoDPoint temp = new TwoDPoint(3, 4);
pointC = temp;
// True:
Console.WriteLine("pointA == (pointC = 3,4) = {0}", pointA == pointC);
pointD = temp;
// True:
Console.WriteLine("pointD == (pointC = 3,4) = {0}", pointD == pointC);
// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
/* Output:
pointA.Equals(pointB) = True
pointA == pointB = True
Object.Equals(pointA, pointB) = True
pointA.Equals(null) = False
(pointA == null) = False
(pointA != null) = True
pointA.Equals(i) = False
pointE.Equals(list[0]): True
pointA == (pointC = null) = False
pointC == pointD = True
pointA == (pointC = 3,4) = True
pointD == (pointC = 3,4) = True
*/
}
Dla struktur, domyślna implementacja z Object.Equals(Object) (która jest zastąpiona wersja w System.ValueType) wykonuje sprawdzanie równości wartości przy użyciu odbicie na porównywaniu wartości każdego pole typu.Kiedy Realizator zastępuje wirtualnego Equals metoda w stuct celem jest zapewnienie bardziej skuteczne środki wykonywania wartość wyboru równości i opcjonalnie podstawę porównania podzestawu struct pole lub właściwości.
== i ! = operatorów nie może działać na struct, chyba że struktura overloads wyraźnie je.