Cara menentukan kesetaraan nilai untuk kelas atau struktur (Panduan Pemrograman C#)
Rekaman secara otomatis menerapkan kesetaraan nilai. Pertimbangkan untuk menentukan record
alih-alih class
ketika jenis Anda memodelkan data dan harus menerapkan kesetaraan nilai.
Saat Anda menentukan kelas atau struktur, Anda memutuskan apakah masuk akal untuk membuat definisi kustom kesetaraan nilai (atau yang setara) untuk jenis tersebut. Biasanya, Anda menerapkan kesetaraan nilai ketika Anda ingin menambahkan objek jenis ke koleksi, atau ketika tujuan utamanya adalah untuk menyimpan sekumpulan bidang atau properti. Anda dapat mendasarkan definisi kesetaraan nilai Anda pada perbandingan semua bidang dan properti dalam jenis, atau Anda dapat mendasarkan definisi pada subset.
Dalam kedua kasus, dan dalam kedua kelas dan struktur, implementasi Anda harus mengikuti lima jaminan kesetaraan (untuk aturan berikut, asumsikan bahwa x
, y
dan z
tidak null):
Properti refleksif:
x.Equals(x)
mengembalikantrue
.Properti simetris:
x.Equals(y)
mengembalikan nilai yang sama dengany.Equals(x)
.Properti transitif: jika
(x.Equals(y) && y.Equals(z))
mengembalikantrue
, makax.Equals(z)
mengembalikantrue
.Pemanggilan
x.Equals(y)
berturut-turut mengembalikan nilai yang sama selama objek yang dirujuk oleh x dan y tidak dimodifikasi.Nilai non-null tidak sama dengan null. Namun,
x.Equals(y)
menjadi pengecualian ketikax
null. Itu melanggar aturan 1 atau 2, tergantung pada argumen padaEquals
.
Setiap struktur yang Anda tentukan sudah memiliki implementasi default kesetaraan nilai yang diwarisinya dari System.ValueType ambil alih terhadap metode Object.Equals(Object). Implementasi ini menggunakan refleksi untuk memeriksa semua bidang dan properti dalam jenis. Meskipun implementasi ini menciptakan hasil yang benar, itu justru relatif lambat dibandingkan dengan implementasi kustom yang Anda tulis khusus untuk jenis tersebut.
Detail implementasi untuk kesetaraan nilai berbeda untuk kelas dan struktur. Namun, kelas dan struktur memerlukan langkah-langkah dasar yang sama untuk menerapkan kesetaraan:
Mengambil alih metode virtualObject.Equals(Object). Dalam kebanyakan kasus, implementasi Anda terhadap
bool Equals( object obj )
hanya boleh memanggil metode khusus jenisEquals
yang merupakan implementasi antarmuka System.IEquatable<T>. (Lihat langkah 2.)Terapkan antarmuka System.IEquatable<T> dengan menyediakan metode khusus jenis
Equals
. Di sinilah perbandingan kesetaraan aktual dilakukan. Misalnya, Anda mungkin memutuskan untuk menentukan kesetaraan dengan hanya membandingkan satu atau dua bidang dalam jenis Anda. Jangan membuat pengecualian dariEquals
. Untuk kelas yang terkait dengan pewarisan:Metode ini hanya boleh memeriksa bidang yang dideklarasikan dalam kelas. Ini harus memanggil
base.Equals
untuk memeriksa bidang yang ada di kelas dasar. (Jangan panggilbase.Equals
jika jenis mewarisi langsung dari Object, karena implementasi Object dari Object.Equals(Object) melakukan pemeriksaan kesetaraan referensi.)Dua variabel harus dianggap sama hanya jika jenis run-time variabel yang dibandingkan sama. Selain itu, pastikan bahwa
IEquatable
implementasiEquals
metode untuk jenis run-time digunakan jika jenis run-time dan compile-time variabel berbeda. Salah satu strategi untuk memastikan jenis run-time selalu dibandingkan dengan benar adalah hanya menerapkanIEquatable
disealed
kelas. Untuk informasi selengkapnya, lihat contoh kelas selanjutnya di artikel ini.
Opsional tetapi disarankan: Kelebihan beban operator == dan !=.
Ambil alih Object.GetHashCode sehingga dua objek yang memiliki kesetaraan nilai menghasilkan hash yang sama.
Opsional: Untuk mendukung definisi untuk "lebih dari" atau "kurang dari," terapkan antarmuka IComparable<T> untuk jenis Anda, dan juga kelebihan beban operator <= dan >=.
Catatan
Anda dapat menggunakan rekaman untuk mendapatkan semantik kesetaraan nilai tanpa kode boilerplate yang tidak perlu.
Contoh kelas
Contoh berikut menunjukkan cara menerapkan kesetaraan nilai dalam kelas (jenis referensi).
namespace ValueEqualityClass;
class TwoDPoint : IEquatable<TwoDPoint>
{
public int X { get; private set; }
public int Y { get; private set; }
public TwoDPoint(int x, int y)
{
if (x is (< 1 or > 2000) || y is (< 1 or > 2000))
{
throw new ArgumentException("Point must be in range 1 - 2000");
}
this.X = x;
this.Y = y;
}
public override bool Equals(object obj) => this.Equals(obj as TwoDPoint);
public bool Equals(TwoDPoint p)
{
if (p is 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() => (X, Y).GetHashCode();
public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
{
if (lhs is null)
{
if (rhs is null)
{
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) => !(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 ArgumentException("Point must be in range 1 - 2000");
}
this.Z = z;
}
public override bool Equals(object obj) => this.Equals(obj as ThreeDPoint);
public bool Equals(ThreeDPoint p)
{
if (p is 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() => (X, Y, Z).GetHashCode();
public static bool operator ==(ThreeDPoint lhs, ThreeDPoint rhs)
{
if (lhs is null)
{
if (rhs is 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) => !(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.
Console.WriteLine("Press any key to exit.");
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
*/
Pada kelas (jenis referensi), implementasi default pada kedua metode Object.Equals(Object) melakukan perbandingan kesetaraan referensi, bukan pemeriksaan kesetaraan nilai. Ketika pelaksana mengambil alih metode virtual, tujuannya adalah untuk memberinya nilai semantik kesetaraan.
Operator ==
dan !=
dapat digunakan dengan kelas bahkan jika kelas tidak membebaninya. Namun, perilaku default berfungsi untuk melakukan pemeriksaan kesetaraan referensi. Dalam kelas, jika Anda membebani metode Equals
Secara berlebih, Anda harus membebani operator ==
dan !=
, tetapi tidak diperlukan.
Penting
Contoh kode sebelumnya mungkin tidak menangani setiap skenario pewarisan seperti yang Anda harapkan. Pertimbangkan gambar berikut:
TwoDPoint p1 = new ThreeDPoint(1, 2, 3);
TwoDPoint p2 = new ThreeDPoint(1, 2, 4);
Console.WriteLine(p1.Equals(p2)); // output: True
Kode ini melaporkan bahwa p1
sama dengan p2
meskipun ada perbedaan pada nilai z
. Perbedaan diabaikan karena pengompilasi memilih implementasi TwoDPoint
berdasarkan jenis waktu kompilasi IEquatable
.
Kesetaraan nilai bawaan jenis record
menangani skenario seperti ini dengan benar. Jika TwoDPoint
dan ThreeDPoint
adalah jenis record
, hasil dari p1.Equals(p2)
adalah False
. Untuk informasi selengkapnya, lihat Kesetaraan dalam record
hierarki pewarisan jenis.
Contoh struktur
Contoh berikut menunjukkan cara menerapkan kesetaraan nilai dalam kelas (jenis nilai):
namespace ValueEqualityStruct
{
struct TwoDPoint : IEquatable<TwoDPoint>
{
public int X { get; private set; }
public int Y { get; private set; }
public TwoDPoint(int x, int y)
: this()
{
if (x is (< 1 or > 2000) || y is (< 1 or > 2000))
{
throw new ArgumentException("Point must be in range 1 - 2000");
}
X = x;
Y = y;
}
public override bool Equals(object? obj) => obj is TwoDPoint other && this.Equals(other);
public bool Equals(TwoDPoint p) => X == p.X && Y == p.Y;
public override int GetHashCode() => (X, Y).GetHashCode();
public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs) => lhs.Equals(rhs);
public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs) => !(lhs == rhs);
}
class Program
{
static void Main(string[] args)
{
TwoDPoint pointA = new TwoDPoint(3, 4);
TwoDPoint pointB = new TwoDPoint(3, 4);
int i = 5;
// 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("pointA.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);
Console.WriteLine("Press any key to exit.");
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
*/
}
Untuk struktur, implementasi default Object.Equals(Object) (yang merupakan versi yang ditimpa pada System.ValueType) melakukan pemeriksaan kesetaraan nilai dengan menggunakan pantulan untuk membandingkan nilai setiap bidang dalam jenis. Ketika pelaksana mengambil alih metode virtual Equals
dalam struktur, tujuannya adalah untuk memberikan cara yang lebih efisien untuk melakukan pemeriksaan kesetaraan nilai dan secara opsional untuk mendasarkan perbandingan pada beberapa subset bidang atau properti struct.
Operator == dan != tidak dapat beroperasi pada struktur kecuali struktur secara eksplisit membebani mereka.
Lihat juga
Saran dan Komentar
https://aka.ms/ContentUserFeedback.
Segera hadir: Sepanjang tahun 2024 kami akan menghentikan penggunaan GitHub Issues sebagai mekanisme umpan balik untuk konten dan menggantinya dengan sistem umpan balik baru. Untuk mengetahui informasi selengkapnya, lihat:Kirim dan lihat umpan balik untuk