يوضّح التمرين البرمجي عددًا من خصائص التساوي في C#:
Object.Equals()== و !=، ويُعدّ هذا أكثر شيوعًا في الاستخدام من استعمال طريقة Equals() المتاحة أيضًا مع هذه الأنواع. وينبغي لمبرمجي Java، عند التعامل مع السلاسل النصية، أن يكونوا منتبهين إلى أن == يقارن بالقيمة في C#، لكنه يقارن بالمرجع في Java عند عودتهم إلى لغتهم السابقة.Equals() الموروثة من object. وإذا كان هدفك من اختبار التساوي هو التأكد من أن كائنين هما نفس النسخة تمامًا، فسيكفي الاعتماد على تطبيق object. وإن لم يكن كذلك، فستحتاج إلى تجاوز object.Equals().Equals().Equals() المتجاوَز على اختبارات تساوٍ لأعضاء من أنواع بسيطة باستخدام ==، ولأنواع مرجعية عبر استدعاءات متكررة لـ Equals().class StatusBar
{
private readonly int width = 200, height = 20;
public override bool Equals(object other)
{
// ... null and type checks and performance optimisations
return width == (other as StatusBar).width && height == (other as StatusBar).height;
}
}
class Window
{
private readonly string title = "Main";
private readonly StatusBar statusBar = new StatusBar();
public override bool Equals(object other)
{
// ... null and type checks and performance optimisations
return title == (other as Window).title && statusBar.Equals((other as Window).statusBar);
}
}
object.ReferenceEquals() لمقارنة كائنين والكشف عمّا إذا كانا نفس النسخة الواحدة. وهذا يمنح وضوحًا، وهو أمر ضروري حيث يكون قد جُوّز Equals() و == أو حُمّلا تحميلًا زائدًا.var winA = new Window(); // above code shows that all windows are equal
var winB = new Window();
ReferenceEquals(winA, winB);
// => false
var winC = winA;
ReferenceEquals(winA, winC);
// => true
public override bool Equals(object obj)، تولّد بيئات التطوير المتكاملة عادةً التحميل الزائد protected bool Equals(FacialFeatures other) لاستخدامه عند وجود وراثة. ويمكن للصنف المشتق أن يستدعي Equals() في الصنف الأساس ثم يضيف اختباره الخاص.== إلا إذا كنت قد حمّلت العامل == تحميلًا زائدًا، وكذلك طريقة Equals() في صنفك (انظر تمرين operator-overloading)، أو كنت تهتم فقط بأن المراجع متساوية.struct في تمرين structs.tuple في تمرين tuples.protected bool Equals(T other)
{
return field1 == other.field1 && field2.Equals(other.field2);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((T) obj);
}
SequenceEquals()، لكن في غياب LINQ يصبح الأمر مسألة المرور على المجموعتين معًا ومقارنة العناصر واحدًا واحدًا.== و != مع أصنافك الخاصة، انظر تمرين operator-overloading.Object.GetHashCode()object.GetHashCode() رمز تجزئة على هيئة عدد صحيح بطول 32 بت. وتستخدم أصناف القواميس والمجموعات، مثل Dictionary<T> و HashSet<T>، رمز التجزئة لتخزين الكائنات واسترجاعها بأداء عالٍ. وفي حالة القواميس تتعلق التجزئة بالمفاتيح.Equals() فستتجاوز GetHashCode() أيضًا. وهناك علاقة بين Equals() و GetHashCode() يجب أن تظل صحيحة ليعمل سلوك أصناف القواميس ومجموعات التجزئة وأي أصناف أخرى تستخدم رمز تجزئة بشكل صحيح. ويُتوقّع منك أن تطبّق الطريقة بحيث لا تنصبّ أي فخاخ للقائمين على الصيانة ممن قد يضيفون مجموعة قائمة على رمز التجزئة في وقت لاحق.Equal() تُرجع صحيحًا)، فيجب أن تُرجع GetHashCode() للكائنين القيمة نفسها. ولا ينطبق هذا في الاتجاه المعاكس، فهو ليس متماثلًا. تخيّل دالة بحث تذهب أولًا إلى «دلو» استنادًا إلى رمز التجزئة، ثم تختار العنصر المعيّن باستخدام اختبار التساوي.HashCode.Combine() وتمرير القيم المستخدمة في اختبار التساوي (أو مجموعة فرعية منها). وضع في اعتبارك أنه كلما زادت المعلومات التي تمرّرها إلى Combine()، كان تطبيق التجزئة أسرع أداءً على الأرجح.public class Assessment
{
private int rating;
private Person boss;
public override int GetHashCode()
{
return HashCode.Combine(rating, boss);
}
}
لتحسين الأداء قليلًا، خصوصًا عندما تنتمي الكائنات إلى مجموعات، يمكنك إضافة عضو محمّل تحميلًا زائدًا public bool Equals(T other).
سوف يوفّر هذا قدرًا من فحص الأنواع للأنواع المرجعية، ويوفّر خطوة تغليف (boxing) للأنواع القيمية، إذ لن تحتاج إلى تحويلها إلى كائن (تغليفها) كوسيط لـ public override bool Equals(object other).
إذا أضفت الواجهة IEquatable<T> إلى صنفك، فسيستلزم ذلك تطبيق التحميل الزائد. وما لم يكن الكود لديك يحتوي على إجراءات تأخذ كائنات من النوع IEquatable<T> (ويُفترض أنها تفعل شيئًا مهمًا يتعلق بالتساوي بصرف النظر عن الصنف المطبِّق)، فلا يوجد في الحقيقة سبب مُقنِع آخر لتضمين الواجهة.
IEqualityComparer<T>إذا كان لديك صنف يمكن التعرف عليه بشكل فريد بأسلوبين مختلفين، كصنف Person له SSID وعنوان بريد إلكتروني فريد، فإن .NET يوفّر وسيلة تتيح لمجموعتين مختلفتين استخدام اختبارات مختلفة لرمز التجزئة والتساوي. ويمكن لكل منهما أن يكون له تطبيق مختلف لـ IEqualityComparer<T> بطريقة Equals() وطريقة GetHashCode() خاصتين به. ويمكن أن يكون لديك قاموس مفتاحه SSID وآخر مفتاحه عنوان البريد الإلكتروني.
وحين يكون IEqualityComparer<T> قيد الاستخدام، فستظل عادةً بحاجة إلى تجاوز Equals() و GetHashCode() في صنف العنصر لديك لتجنّب مشكلات خارج أصناف المجموعات.
ومن الاعتبارات عند استخدام IEqualityComparer<T> أن الطرق الخاصة وما شابهها لن تكون متاحة لاختبار التساوي.
وإذا كانت هناك مجموعة مجزّأة واحدة فقط قيد الاستخدام، فقد يكون من الأفضل تجنّب IEqualityComparer<T> وتغليف التساوي ورمز التجزئة داخل الكائن نفسه. وليس مثاليًا أصلًا ألا يتمكّن المترجم من فرض تبعية أساسية كالتي بين الكائن والمجموعة المجزّأة، لكن مع وجود منطق التجزئة والمقارنة في مكان آخر سيصبح من الأسهل على القائم بالصيانة إجراء تغييرات على أعضاء صنف دون اعتبار لعواقب ذلك على المجموعة.
من الأمور التي قد تربك المبرمج غير الحذر اختبار تساوي قيم الفاصلة العائمة. وتُناقش هذه المسألة في مستند about.md لمفهوم floating-point-numbers.
يوضّح هذا المقال بعض القرارات التي يجب اتخاذها فيما يتعلق بالتساوي والوراثة. ولن تهمّك هذه القرارات إلا إذا كنت تتعامل مع نسخ من صنف وصنف مشتق وتحتاج إلى اختبار التساوي بينهما.