Cho rằng các tập hợp như System.Collections.Generic.HashSet<>
chấp nhận null
như một thành viên tập hợp, người ta có thể hỏi mã băm của nó null
nên là gì. Có vẻ như khung sử dụng 0
:
// nullable struct type
int? i = null;
i.GetHashCode(); // gives 0
EqualityComparer<int?>.Default.GetHashCode(i); // gives 0
// class type
CultureInfo c = null;
EqualityComparer<CultureInfo>.Default.GetHashCode(c); // gives 0
Điều này có thể (một chút) có vấn đề với các enum nullable. Nếu chúng ta xác định
enum Season
{
Spring,
Summer,
Autumn,
Winter,
}
thì Nullable<Season>
(còn được gọi là Season?
) có thể chỉ nhận năm giá trị, nhưng hai trong số đó, cụ thể là null
và Season.Spring
, có cùng mã băm.
Thật hấp dẫn khi viết một trình so sánh bình đẳng "tốt hơn" như thế này:
class NewNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
public override bool Equals(T? x, T? y)
{
return Default.Equals(x, y);
}
public override int GetHashCode(T? x)
{
return x.HasValue ? Default.GetHashCode(x) : -1;
}
}
Nhưng có lý do gì khiến mã băm của null
phải như 0
vậy không?
CHỈNH SỬA / BỔ SUNG:
Một số người dường như nghĩ rằng đây là về việc ghi đè Object.GetHashCode()
. Nó thực sự không phải, thực sự. (Các tác giả của NET đã thực hiện một ghi đè GetHashCode()
trong Nullable<>
struct mà là có liên quan, mặc dù.) Một thực hiện sử dụng bằng văn bản của parameterless GetHashCode()
không bao giờ có thể xử lý tình huống nơi mà các đối tượng có mã băm chúng ta tìm kiếm là null
.
Đây là về việc triển khai phương thức trừu tượng EqualityComparer<T>.GetHashCode(T)
hoặc cách khác thực hiện phương thức giao diện IEqualityComparer<T>.GetHashCode(T)
. Bây giờ, trong khi tạo các liên kết này đến MSDN, tôi thấy rằng nó nói ở đó rằng các phương thức này ném một ArgumentNullException
đối số duy nhất của chúng nếu là null
. Đây chắc chắn phải là một sai lầm trên MSDN? Không có triển khai riêng của .NET nào có ngoại lệ. Ném trong trường hợp đó sẽ phá vỡ hiệu quả bất kỳ nỗ lực nào để thêm null
vào a HashSet<>
. Trừ khi HashSet<>
làm điều gì đó phi thường khi giao dịch với một null
mặt hàng (tôi sẽ phải kiểm tra điều đó).
CHỈNH SỬA / BỔ SUNG MỚI:
Bây giờ tôi đã thử gỡ lỗi. Với HashSet<>
, tôi có thể xác nhận rằng với trình so sánh bình đẳng mặc định, các giá trị Season.Spring
và null
sẽ kết thúc trong cùng một nhóm. Điều này có thể được xác định bằng cách kiểm tra rất cẩn thận các thành viên mảng private m_buckets
và m_slots
. Lưu ý rằng theo thiết kế, các chỉ số luôn được bù trừ bởi một.
Tuy nhiên, đoạn mã tôi đưa ra ở trên không khắc phục được điều này. Hóa ra, HashSet<>
thậm chí sẽ không bao giờ hỏi người so sánh bình đẳng khi giá trị là null
. Đây là từ mã nguồn của HashSet<>
:
// Workaround Comparers that throw ArgumentNullException for GetHashCode(null).
private int InternalGetHashCode(T item) {
if (item == null) {
return 0;
}
return m_comparer.GetHashCode(item) & Lower31BitMask;
}
Điều này có nghĩa là, ít nhất HashSet<>
, thậm chí không thể thay đổi hàm băm của null
. Thay vào đó, một giải pháp là thay đổi hàm băm của tất cả các giá trị khác, như sau:
class NewerNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
public override bool Equals(T? x, T? y)
{
return Default.Equals(x, y);
}
public override int GetHashCode(T? x)
{
return x.HasValue ? 1 + Default.GetHashCode(x) : /* not seen by HashSet: */ 0;
}
}