Aracılığıyla paylaş


Bir sınıf veya yapı için değer eşitliğini tanımlama (C# Programlama Kılavuzu)

Kayıtlar otomatik olarak değer eşitliğini uygular. Türünüz veri modellerken ve değer eşitliği uygulaması gerektiğinde bir class yerine bir record tanımlamayı düşünün.

Bir sınıf veya yapı tanımladığınızda, tür için değer eşitliği (veya denkliği) için özel bir tanım oluşturmanın mantıklı olup olmadığına karar verirsiniz. Genellikle, bir koleksiyona türdeki nesneleri eklemeyi beklediğinizde veya birincil amacı bir alan veya özellik kümesi depolamak olduğunda değer eşitliği uygularsınız. Değer eşitliği tanımınızı türdeki tüm alanların ve özelliklerin karşılaştırmasını temel alabilir veya tanımı bir alt kümeye dayandırabilirsiniz.

Her iki durumda da ve hem sınıflarda hem de yapılarda uygulamanız beş denklik garantisine uymalıdır (aşağıdaki kurallar için , x ve y değerlerinin znull olmadığını varsayın):

  1. Esnek özelliği: x.Equals(x) döndürür true.

  2. Simetrik özellik: x.Equals(y) ile aynı değeri y.Equals(x)döndürür.

  3. Geçişli özellik: Eğer (x.Equals(y) && y.Equals(z))true döndürürse, o zaman x.Equals(z)true döndürür.

  4. x ve y tarafından başvuruda bulunan nesneler değiştirilmediği sürece birbirini izleyen çağrılar x.Equals(y) aynı değeri döndürür.

  5. Null olmayan değerler null değerine eşit değildir. Ancak null x.Equals(y) olduğunda x bir özel durum oluşturur. Bu, Equals bağımsız değişkenine bağlı olarak 1 veya 2. kuralı bozar.

Tanımladığınız herhangi bir yapı, Object.Equals(Object) yönteminin System.ValueType geçersiz kılınmasından devraldığı varsayılan değer eşitliği uygulamasına zaten sahiptir. Bu uygulama, türdeki tüm alanları ve özellikleri incelemek için yansıma kullanır. Bu uygulama doğru sonuçlar üretse de, türü için özel olarak yazdığınız özel bir uygulamayla karşılaştırıldığında nispeten yavaştır.

Değer eşitliği için uygulama ayrıntıları sınıflar ve yapılar için farklıdır. Ancak, hem sınıflar hem de yapılar eşitlik uygulamak için aynı temel adımları gerektirir:

  1. SanalObject.Equals(Object) yöntemi geçersiz kılın. Çoğu durumda, bool Equals( object obj ) uygulamanız, System.IEquatable<T> arabiriminin bir uygulaması olan tür özel Equals yöntemini çağırmalıdır. (Bkz. 2. adım.)

  2. Türe System.IEquatable<T> özgü Equals bir yöntem sağlayarak arabirimini uygulayın. Burada gerçek denklik karşılaştırması gerçekleştirilir. Örneğin, türünizdeki yalnızca bir veya iki alanı karşılaştırarak eşitlik tanımlamaya karar vekleyebilirsiniz. Equals'den özel durum fırlatmayın. Kalıtım yoluyla birbirine bağlı sınıflar için:

    • Bu yöntem yalnızca sınıfında bildirilen alanları incelemelidir. Temel sınıftaki alanları incelemek için base.Equals çağrı yapmalıdır. (Doğrudan Object öğesinden tür devralıyorsa base.Equals çağırmayın, çünkü Object'nin Object.Equals(Object) üzerindeki uygulaması bir başvuru eşitliği denetimi yapar.)

    • yalnızca karşılaştırılan değişkenlerin çalışma zamanı türleri aynıysa iki değişken eşit kabul edilmelidir. Ayrıca, bir değişkenin çalışma zamanı ve derleme zamanı türleri farklıysa, IEquatable yöntemi için çalışma zamanı türünün uygulanmasının Equals kullanıldığından emin olun. Çalışma zamanı türlerinin her zaman doğru karşılaştırıldığından emin olmak için bir strateji yalnızca sınıflarda IEquatable uygulamaktırsealed. Daha fazla bilgi için bu makalenin devamında yer alan sınıf örneğine bakın.

  3. İsteğe bağlı ama önerilir: == ve != işleçlerini aşırı yükleyin.

  4. Object.GetHashCode'yi, değer açısından eşit olan iki nesnenin aynı karma kodu üretmesini sağlamak için geçersiz kılın.

  5. İsteğe bağlı: "büyüktür" veya "küçüktür" tanımlarını desteklemek için, türünüz için IComparable<T> arabirimini uygulayın ve ayrıca <= ve >= işleçlerini aşırı yükleyin.

Not

Gereksiz bir ortak kod olmadan değer eşitliği semantiği elde etmek için kayıtları kullanabilirsiniz.

Sınıf örneği

Aşağıdaki örnekte, bir sınıfta değer eşitliğinin nasıl uygulandığı gösterilmektedir (başvuru türü).

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) = {pointA.Equals(pointB)}");
        Console.WriteLine($"pointA == pointB = {pointA == pointB}");
        Console.WriteLine($"null comparison = {pointA.Equals(pointC)}");
        Console.WriteLine($"Compare to some other type = {pointA.Equals(i)}");

        TwoDPoint pointD = null;
        TwoDPoint pointE = null;

        Console.WriteLine($"Two null TwoDPoints are equal: {pointD == pointE}");

        pointE = new TwoDPoint(3, 4);
        Console.WriteLine($"(pointE == pointA) = {pointE == pointA}");
        Console.WriteLine($"(pointA == pointE) = {pointA == pointE}");
        Console.WriteLine($"(pointA != pointE) = {pointA != pointE}");

        System.Collections.ArrayList list = new System.Collections.ArrayList();
        list.Add(new ThreeDPoint(3, 4, 5));
        Console.WriteLine($"pointE.Equals(list[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
*/

Sınıflarda (başvuru türleri), her iki Object.Equals(Object) yöntemin de varsayılan uygulaması, değer eşitliği kontrolü değil, başvuru eşitliği karşılaştırması gerçekleştirir. Bir uygulayıcı sanal yöntemi geçersiz kıldığında, amaç değer eşitliği semantiği vermektir.

== ve != işleçleri, sınıfı aşırı yüklemese bile sınıflarla birlikte kullanılabilir. Ancak, varsayılan davranış bir başvuru eşitliği denetimi gerçekleştirmektir. Bir sınıfta, eğer Equals yöntemini aşırı yüklerseniz, == ve != işleçlerini de aşırı yüklemelisiniz, ancak bu zorunlu değildir.

Önemli

Yukarıdaki örnek kod, her devralma senaryolarını beklediğiniz gibi işlemeyebilir. Aşağıdaki kodu inceleyin:

TwoDPoint p1 = new ThreeDPoint(1, 2, 3);
TwoDPoint p2 = new ThreeDPoint(1, 2, 4);
Console.WriteLine(p1.Equals(p2)); // output: True

Bu kod, farklı z değerlerine rağmen p1'in p2'e eşit olduğunu bildirir. Derleyici, derleme zamanı türüne göre TwoDPoint'in uygulamasını seçtiği için IEquatable farkı yoksayılır.

Türlerin record yerleşik değer eşitliği, bunun gibi senaryoları doğru işler. Eğer TwoDPoint ve ThreeDPoint, record türleri olsaydı, p1.Equals(p2)'nin sonucu False olurdu. Daha fazla bilgi için bkz. Tür devralma hiyerarşilerinde recordeşitlik.

Yapı örneği

Aşağıdaki örnekte, bir yapıda değer eşitliğinin (değer türü) nasıl uygulanacakları gösterilmektedir:

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) = {pointA.Equals(pointB)}");
            // True:
            Console.WriteLine($"pointA == pointB = {pointA == pointB}");
            // True:
            Console.WriteLine($"object.Equals(pointA, pointB) = {object.Equals(pointA, pointB)}");
            // False:
            Console.WriteLine($"pointA.Equals(null) = {pointA.Equals(null)}");
            // False:
            Console.WriteLine($"(pointA == null) = {pointA == null}");
            // True:
            Console.WriteLine($"(pointA != null) = {pointA != null}");
            // False:
            Console.WriteLine($"pointA.Equals(i) = {pointA.Equals(i)}");
            // CS0019:
            // Console.WriteLine($"pointA == i = {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]): {pointA.Equals(list[0])}");

            // Compare nullable to nullable and to non-nullable.
            TwoDPoint? pointC = null;
            TwoDPoint? pointD = null;
            // False:
            Console.WriteLine($"pointA == (pointC = null) = {pointA == pointC}");
            // True:
            Console.WriteLine($"pointC == pointD = {pointC == pointD}");

            TwoDPoint temp = new TwoDPoint(3, 4);
            pointC = temp;
            // True:
            Console.WriteLine($"pointA == (pointC = 3,4) = {pointA == pointC}");

            pointD = temp;
            // True:
            Console.WriteLine($"pointD == (pointC = 3,4) = {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
    */
}

Yapılar için, Object.Equals(Object)'ın varsayılan uygulaması (bu, System.ValueType içindeki geçersiz kılınan sürümdür), türdeki her alanın değerlerini yansımayı kullanarak karşılaştırarak bir değer eşitliği denetimi gerçekleştirir. Bir uygulayıcı bir yapıdaki sanal Equals yöntemi geçersiz kıldığında, amaç değer eşitliği denetimini gerçekleştirmek için daha verimli bir yöntem sağlamak ve isteğe bağlı olarak karşılaştırmayı yapı alanlarının veya özelliklerinin bazı alt kümesine dayandırmaktır.

== ve != işleçleri, yapı açıkça aşırı yüklemediği sürece bir yapı üzerinde çalışamaz.

Ayrıca bkz.