تجاوز GetHashCode
حافظوا على اتساق المساواة وإنشاء التجزئة
تجاوز GetHashCode درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.
لماذا يُعد GetHashCode مهمًا
تستخدم المجموعات المعتمدة على التجزئة، مثل Dictionary وHashSet، GetHashCode لتوزيع العناصر على الحاويات. فإذا أعدت تعريف Equals دون GetHashCode، فقد تفشل هذه المجموعات في العثور على العناصر المتساوية.
القاعدة الذهبية
العقد بسيط: إذا كان كائنان متساويين، فيجب أن يعيدا رمز التجزئة نفسه. أما العكس فليس مطلوبًا؛ إذ يمكن أن تشترك كائنات مختلفة في رمز تجزئة واحد (تصادم).
using System;
public struct Point
{
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
public override bool Equals(object obj) => obj is Point p && p.X == X && p.Y == Y;
public override int GetHashCode() => HashCode.Combine(X, Y);
}
public class Program
{
public static void Main()
{
var a = new Point(1, 2);
var b = new Point(1, 2);
Console.WriteLine(a.Equals(b));
Console.WriteLine(a.GetHashCode() == b.GetHashCode());
}
}استخدم HashCode.Combine
تجمع الأداة المساعدة HashCode.Combine قيم عدة حقول في تجزئة موزعة جيدًا. وهي الطريقة الحديثة الموصى بها لتنفيذ GetHashCode.
using System;
public struct Color
{
public int R, G, B;
public Color(int r, int g, int b) { R = r; G = g; B = b; }
public override bool Equals(object obj) => obj is Color c && c.R == R && c.G == G && c.B == B;
public override int GetHashCode() => HashCode.Combine(R, G, B);
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Color(255, 0, 0).GetHashCode() == new Color(255, 0, 0).GetHashCode());
}
}أعد تعريف المساواة والتجزئة معًا
أعد دائمًا تعريف Equals وGetHashCode معًا، باستخدام الحقول نفسها في كليهما. فاستخدام حقول مختلفة يجعل الكائنات المتساوية تنتج رموز تجزئة مختلفة.
using System;
public class Book
{
public string Title;
public int Year;
public Book(string title, int year) { Title = title; Year = year; }
public override bool Equals(object obj)
=> obj is Book b && b.Title == Title && b.Year == Year;
public override int GetHashCode() => HashCode.Combine(Title, Year);
}
public class Program
{
public static void Main()
{
var a = new Book("C#", 2020);
var b = new Book("C#", 2020);
Console.WriteLine(a.GetHashCode() == b.GetHashCode());
}
}لماذا تحتاج القواميس إليه
تُجزّئ Dictionary المفتاح أولًا للعثور على حاوية، ثم تستخدم Equals داخل تلك الحاوية. وتؤدي التجزئة الخاطئة إلى توجيه البحث إلى الحاوية الخطأ، فلا يُعثر على المفتاح مطلقًا.
using System;
using System.Collections.Generic;
public struct Coord
{
public int X, Y;
public Coord(int x, int y) { X = x; Y = y; }
public override bool Equals(object obj) => obj is Coord c && c.X == X && c.Y == Y;
public override int GetHashCode() => HashCode.Combine(X, Y);
}
public class Program
{
public static void Main()
{
var map = new Dictionary<Coord, string> { [new Coord(2, 3)] = "hit" };
Console.WriteLine(map[new Coord(2, 3)]);
}
}تجزئة الحقول غير القابلة للتغيير
ينبغي أن تبقى رموز التجزئة ثابتة للكائن طوال وجوده في مجموعة تجزئة. فابنِ التجزئة على الحقول غير القابلة للتغيير؛ إذ إن تغيير المفتاح بعد إدراجه قد يجعل المجموعة تفقد القدرة على تتبعه.
using System;
public class Account
{
public readonly int Id; // immutable, safe to hash
public string Nickname; // mutable, do not hash
public Account(int id, string nick) { Id = id; Nickname = nick; }
public override bool Equals(object obj) => obj is Account a && a.Id == Id;
public override int GetHashCode() => Id.GetHashCode();
}
public class Program
{
public static void Main()
{
var acc = new Account(42, "old");
int h1 = acc.GetHashCode();
acc.Nickname = "new"; // does not change the hash
Console.WriteLine(h1 == acc.GetHashCode());
}
}التعامل مع الحقول الفارغة
عندما يمكن أن يكون الحقل null، تتعامل HashCode.Combine معه بأمان. وإذا حسبت التجزئة يدويًا، فتحقق من null لتجنب NullReferenceException.
using System;
public class Tag
{
public string Name;
public Tag(string name) { Name = name; }
public override bool Equals(object obj) => obj is Tag t && t.Name == Name;
public override int GetHashCode() => HashCode.Combine(Name); // null-safe
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Tag(null).GetHashCode() == new Tag(null).GetHashCode());
}
}التصادمات طبيعية
رموز التجزئة مؤلفة من 32 بت، لذا لا مفر من التصادمات مع مجموعات البيانات الكبيرة. تؤدي التجزئة الجيدة فقط إلى توزيع القيم بحيث تبقى التصادمات نادرة؛ ويتولى Equals حل أي تصادم يحدث.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var set = new HashSet<int>();
for (int i = 0; i < 5; i++) set.Add(i);
// Equality still works perfectly even though hashing is imperfect in general
Console.WriteLine(set.Contains(3));
Console.WriteLine(set.Contains(99));
}
}تنشئها السجلات لك
ينشئ record تلقائيًا GetHashCode صحيحًا من خصائصه، بما يتوافق مع Equals القائم على القيمة. وهذه أسهل طريقة للحصول على تجزئة صحيحة.
using System;
using System.Collections.Generic;
public record Coord(int X, int Y);
public class Program
{
public static void Main()
{
var set = new HashSet<Coord> { new Coord(1, 1) };
Console.WriteLine(set.Contains(new Coord(1, 1)));
}
}نوع Equatable صحيح
عند جمع كل ذلك: الحقول نفسها في Equals وGetHashCode، ومفاتيح غير قابلة للتغيير، واستخدام HashCode.Combine لدمج القيم. يعمل هذا النوع بلا مشكلات كمفتاح في القاموس.
using System;
using System.Collections.Generic;
public struct ProductKey
{
public string Sku;
public int Variant;
public ProductKey(string sku, int variant) { Sku = sku; Variant = variant; }
public override bool Equals(object obj)
=> obj is ProductKey k && k.Sku == Sku && k.Variant == Variant;
public override int GetHashCode() => HashCode.Combine(Sku, Variant);
}
public class Program
{
public static void Main()
{
var stock = new Dictionary<ProductKey, int>
{
[new ProductKey("ABC", 1)] = 10
};
Console.WriteLine(stock[new ProductKey("ABC", 1)]);
}
}جرّب بنفسك
أثبت صحة العقد: أنشئ مفتاحين متساويين، وتحقق من تطابق المساواة ورموز التجزئة، ثم استخدمهما في مجموعة.
using System;
using System.Collections.Generic;
public struct Name
{
public string First, Last;
public Name(string first, string last) { First = first; Last = last; }
public override bool Equals(object obj) => obj is Name n && n.First == First && n.Last == Last;
public override int GetHashCode() => HashCode.Combine(First, Last);
}
public class Program
{
public static void Main()
{
var a = new Name("Ada", "Lovelace");
var b = new Name("Ada", "Lovelace");
Console.WriteLine(a.Equals(b) + " " + (a.GetHashCode() == b.GetHashCode()));
var people = new HashSet<Name> { a };
Console.WriteLine(people.Contains(b));
}
}اختبار سريع
تذكّر عقد التجزئة.
مراجعة
تحافظ التجزئة الصحيحة على عمل المجموعات المعتمدة على التجزئة.
- يجب أن تعيد الكائنات المتساوية رمز التجزئة نفسه.
- استخدم
HashCode.Combineمع الحقول نفسها المستخدمة فيEquals. - جزّئ الحقول غير القابلة للتغيير حتى تظل المفاتيح قابلة للعثور عليها.
- التصادمات طبيعية؛ ويتولى
Equalsحلها. - تنشئ السجلات تنفيذًا صحيحًا تلقائيًا.
الأسئلة الشائعة
هل درس «تجاوز GetHashCode» مجاني؟
نعم — نص درس «تجاوز GetHashCode» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.
ماذا ستتعلم في «تجاوز GetHashCode»؟
حافظوا على اتساق المساواة وإنشاء التجزئة تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟
لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «تجاوز GetHashCode»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟
نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- تنفيذ IEquatable
- تجاوز GetHashCode
- تنفيذ IComparable
- IComparer للفرز المخصّص