Làm cách nào để kiểm tra giá trị rỗng trong quá tải toán tử '==' mà không cần đệ quy vô hạn?


113

Điều sau sẽ gây ra đệ quy vô hạn trên phương thức nạp chồng toán tử ==

    Foo foo1 = null;
    Foo foo2 = new Foo();
    Assert.IsFalse(foo1 == foo2);

    public static bool operator ==(Foo foo1, Foo foo2) {
        if (foo1 == null) return foo2 == null;
        return foo1.Equals(foo2);
    }

Làm cách nào để kiểm tra giá trị rỗng?

Câu trả lời:


138

Sử dụng ReferenceEquals:

Foo foo1 = null;
Foo foo2 = new Foo();
Assert.IsFalse(foo1 == foo2);

public static bool operator ==(Foo foo1, Foo foo2) {
    if (object.ReferenceEquals(null, foo1))
        return object.ReferenceEquals(null, foo2);
    return foo1.Equals(foo2);
}

Giải pháp này không hoạt động đối vớiAssert.IsFalse(foo2 == foo1);
FIL

Và điều gì có foo1.Equals(foo2)nghĩa là, ví dụ, tôi muốn foo1 == foo2chỉ khi foo1.x == foo2.x && foo1.y == foo2.y? Không phải là trả lời này bỏ qua trường hợp foo1 != nullnhưng foo2 == null?
Daniel

Lưu ý: Giải pháp tương tự với cú pháp đơn giản hơn:if (foo1 is null) return foo2 is null;
Rem

20

Truyền tới đối tượng trong phương thức quá tải:

public static bool operator ==(Foo foo1, Foo foo2) {
    if ((object) foo1 == null) return (object) foo2 == null;
    return foo1.Equals(foo2);
}

1
Chính xác. Cả hai (object)foo1 == nullhoặc foo1 == (object)nullsẽ chuyển sang trạng thái quá tải cài sẵn ==(object, object)chứ không phải quá tải do người dùng xác định ==(Foo, Foo). Nó cũng giống như giải quyết quá tải trên các phương thức.
Jeppe Stig Nielsen

2
Đối với khách truy cập trong tương lai - câu trả lời được chấp nhận là một hàm thực thi đối tượng == of. Điều này về cơ bản giống với câu trả lời được chấp nhận, với một nhược điểm: Nó cần một dàn diễn viên. Vì vậy, câu trả lời đã được accpiled vượt trội hơn.
Mafii

1
@Mafii Dàn diễn viên là hoàn toàn một hoạt động thời gian biên dịch. Vì trình biên dịch biết rằng quá trình truyền không thể bị lỗi, nó không cần kiểm tra bất cứ điều gì trong thời gian chạy. Sự khác biệt giữa các phương pháp là hoàn toàn thẩm mỹ.
Servy

8

Sử dụng ReferenceEquals. Từ các diễn đàn MSDN :

public static bool operator ==(Foo foo1, Foo foo2) {
    if (ReferenceEquals(foo1, null)) return ReferenceEquals(foo2, null);
    if (ReferenceEquals(foo2, null)) return false;
    return foo1.field1 == foo2.field2;
}

4

Thử Object.ReferenceEquals(foo1, null)

Dù sao, tôi không khuyên bạn nên quá tải ==toán tử; nó nên được sử dụng để so sánh các tham chiếu và sử dụng Equalsđể so sánh "ngữ nghĩa".


4

Nếu tôi đã ghi đè bool Equals(object obj)và tôi muốn toán tử ==Foo.Equals(object obj)trả về cùng một giá trị, tôi thường triển !=khai toán tử như sau:

public static bool operator ==(Foo foo1, Foo foo2) {
  return object.Equals(foo1, foo2);
}
public static bool operator !=(Foo foo1, Foo foo2) {
  return !object.Equals(foo1, foo2);
}

==Sau khi thực hiện tất cả các kiểm tra null cho tôi, nhà điều hành sẽ kết thúc cuộc gọi foo1.Equals(foo2)rằng tôi đã ghi đè để thực hiện kiểm tra thực tế nếu cả hai bằng nhau.


Điều này cảm thấy rất thích hợp; nhìn vào việc triển khai Object.Equals(Object, Object)song song với Object.ReferenceEquals(Object, Object), khá rõ ràng là Object.Equals(Object, Object)thực hiện mọi thứ như được đề xuất trong các câu trả lời khác. Tại sao không sử dụng nó?
tne

@tne Vì không có điểm nào quá tải ==toán tử nếu tất cả những gì bạn muốn là hành vi mặc định. Bạn chỉ nên quá tải khi bạn cần triển khai logic so sánh tùy chỉnh, tức là, một cái gì đó hơn là kiểm tra bình đẳng tham chiếu.
Dan Bechard

@Dan Tôi tin rằng bạn đã hiểu sai nhận xét của tôi; trong bối cảnh đã được thiết lập rằng quá tải ==là mong muốn (câu hỏi ngụ ý điều đó), tôi chỉ đơn giản ủng hộ câu trả lời này bằng cách đề xuất rằng Object.Equals(Object, Object)làm cho các thủ thuật khác như sử dụng ReferenceEqualshoặc phôi rõ ràng là không cần thiết (vì vậy "tại sao không sử dụng nó?", "nó" đang Equals(Object, Object)). Ngay cả khi không liên quan, quan điểm của bạn cũng đúng, và tôi sẽ đi xa hơn: chỉ quá tải ==cho các đối tượng mà chúng ta có thể phân loại là "đối tượng giá trị".
tne

@tne Sự khác biệt chính là Object.Equals(Object, Object)lần lượt gọi Object.Equals (Đối tượng) là một phương thức ảo mà Foo có thể sẽ ghi đè. Thực tế là bạn đã đưa một lệnh gọi ảo vào kiểm tra tính bình đẳng của mình có thể ảnh hưởng đến khả năng của trình biên dịch để tối ưu hóa (ví dụ: nội tuyến) các lệnh gọi này. Điều này có lẽ không đáng kể đối với hầu hết các mục đích, nhưng trong một số trường hợp nhất định, một chi phí nhỏ trong toán tử bình đẳng có thể có nghĩa là một chi phí lớn cho các vòng lặp hoặc cấu trúc dữ liệu được sắp xếp.
Dan Bechard

@tne Để biết thêm thông tin về sự phức tạp của việc tối ưu hóa lệnh gọi phương thức ảo, hãy tham khảo stackoverflow.com/questions/530799/… .
Dan Bechard

3

Nếu bạn đang sử dụng C # 7 trở lên, bạn có thể sử dụng đối sánh mẫu hằng số null:

public static bool operator==(Foo foo1, Foo foo2)
{
    if (foo1 is null)
        return foo2 is null;
    return foo1.Equals(foo2);
}

Điều này cung cấp cho bạn mã gọn gàng hơn một chút so với một đối tượng đang gọi .ReferenceEquals (foo1, null)


2
hoặcpublic static bool operator==( Foo foo1, Foo foo2 ) => foo1?.Equals( foo2 ) ?? foo2 is null;
Danko Durbić

3

Thực ra có một cách đơn giản hơn để kiểm tra nulltrong trường hợp này:

if (foo is null)

Đó là nó!

Tính năng này đã được giới thiệu trong C # 7


@Eliasar rất may các câu hỏi không có ngày hết hạn :)
groplex vào

1

Cách tiếp cận của tôi là làm

(object)item == null

mà tôi đang dựa vào objecttoán tử bình đẳng của riêng mình mà không thể sai. Hoặc phương thức tiện ích mở rộng tùy chỉnh (và quá tải):

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null;
}

public static bool IsNull<T>(this T? obj) where T : struct
{
    return !obj.HasValue;
}

hoặc để xử lý nhiều trường hợp hơn, có thể là:

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null || obj == DBNull.Value;
}

Ràng buộc ngăn cản IsNullcác kiểu giá trị. Bây giờ nó ngọt ngào như đang gọi

object obj = new object();
Guid? guid = null; 
bool b = obj.IsNull(); // false
b = guid.IsNull(); // true
2.IsNull(); // error

có nghĩa là tôi có một phong cách nhất quán / không dễ xảy ra lỗi trong việc kiểm tra các giá trị rỗng trong suốt. Tôi cũng nhận thấy rằng (object)item == nullnó nhanh hơn rất nhiều so với một chútObject.ReferenceEquals(item, null) , nhưng chỉ khi nó quan trọng (tôi hiện đang làm việc trên một cái gì đó mà tôi đã tối ưu hóa mọi thứ!).

Để xem hướng dẫn đầy đủ về cách thực hiện kiểm tra bằng nhau, hãy xem "Phương pháp hay nhất" để so sánh hai trường hợp của một loại tham chiếu là gì?


Nitpick: Độc giả nên xem các phụ thuộc của họ trước khi nhảy vào các tính năng như so sánh DbNull, IMO, trường hợp điều này không tạo ra các vấn đề liên quan đến SRP là khá hiếm. Tuy nhiên, chỉ cần chỉ ra mùi mã, nó rất có thể thích hợp.
tne

0

Equals(Object, Object)Phương thức static cho biết liệu hai đối tượng objAobjB, có bằng nhau hay không. Nó cũng cho phép bạn kiểm tra các đối tượng có giá trị nullbằng nhau. Nó so sánh objAobjBbình đẳng như sau:

  • Nó xác định xem hai đối tượng có đại diện cho cùng một tham chiếu đối tượng hay không. Nếu đúng như vậy, phương thức sẽ trả về true. Kiểm tra này tương đương với việc gọi ReferenceEqualsphương thức. Ngoài ra, nếu cả hai objAobjBđều null, phương thức trả về true.
  • Nó xác định xem objAhoặc objBnull. Nếu vậy, nó trả về false. Nếu hai đối tượng không đại diện cho cùng một tham chiếu đối tượng và cũng vậy null, nó sẽ gọi objA.Equals(objB)và trả về kết quả. Điều này có nghĩa là nếu objAghi đè Object.Equals(Object)phương thức, ghi đè này được gọi.

.

public static bool operator ==(Foo objA, Foo objB) {
    return Object.Equals(objA, objB);
}

0

trả lời thêm cho toán tử ghi đè cách so sánh với null chuyển hướng đến đây như một bản sao.

Trong trường hợp điều này đang được thực hiện để hỗ trợ Đối tượng Giá trị, tôi thấy ký hiệu mới rất tiện dụng và muốn đảm bảo rằng chỉ có một nơi duy nhất để so sánh được thực hiện. Cũng tận dụng Object.Equals (A, B) đơn giản hóa việc kiểm tra null.

Điều này sẽ làm quá tải ==,! =, Equals và GetHashCode

    public static bool operator !=(ValueObject self, ValueObject other) => !Equals(self, other);
    public static bool operator ==(ValueObject self, ValueObject other) => Equals(self, other);
    public override bool Equals(object other) => Equals(other as ValueObject );
    public bool Equals(ValueObject other) {
        return !(other is null) && 
               // Value comparisons
               _value == other._value;
    }
    public override int GetHashCode() => _value.GetHashCode();

Đối với các đối tượng phức tạp hơn, hãy thêm các so sánh bổ sung trong Equals và GetHashCode phong phú hơn.


0

Để có một cú pháp hiện đại và cô đọng:

public static bool operator ==(Foo x, Foo y)
{
    return x is null ? y is null : x.Equals(y);
}

public static bool operator !=(Foo x, Foo y)
{
    return x is null ? !(y is null) : !x.Equals(y);
}

-3

Một lỗi phổ biến trong quá tải toán tử == là sử dụng (a == b), (a ==null)hoặc (b == null)để kiểm tra bình đẳng tham khảo. Thay vào đó, điều này dẫn đến cuộc gọi đến toán tử quá tải ==, gây ra infinite loop. Sử dụng ReferenceEqualshoặc ép kiểu thành Đối tượng, để tránh vòng lặp.

kiểm tra cái này

// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))// using ReferenceEquals
{
    return true;
}

// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))// using casting the type to Object
{
    return false;
}

tham khảo Hướng dẫn về Nạp chồng bằng () và toán tử ==


1
Đã có nhiều câu trả lời với tất cả thông tin này. Chúng tôi không cần bản sao thứ 7 của cùng một câu trả lời.
Servy

-5

Bạn có thể thử sử dụng một thuộc tính đối tượng và bắt NullReferenceException kết quả. Nếu thuộc tính bạn cố gắng được kế thừa hoặc ghi đè từ Đối tượng, thì điều này sẽ hoạt động cho bất kỳ lớp nào.

public static bool operator ==(Foo foo1, Foo foo2)
{
    //  check if the left parameter is null
    bool LeftNull = false;
    try { Type temp = a_left.GetType(); }
    catch { LeftNull = true; }

    //  check if the right parameter is null
    bool RightNull = false;
    try { Type temp = a_right.GetType(); }
    catch { RightNull = true; }

    //  null checking results
    if (LeftNull && RightNull) return true;
    else if (LeftNull || RightNull) return false;
    else return foo1.field1 == foo2.field2;
}

Nếu bạn có nhiều đối tượng rỗng thì việc xử lý ngoại lệ có thể là một chi phí lớn.
Kasprzol

2
Haha, tôi đồng ý rằng đây không phải là phương pháp tốt nhất. Sau khi đăng phương pháp này, tôi ngay lập tức sửa đổi dự án hiện tại của mình để sử dụng ReferenceEquals thay thế. Tuy nhiên, mặc dù không tối ưu nhưng nó vẫn hoạt động, và do đó là một câu trả lời hợp lệ cho câu hỏi.
The Digital Gabeg 19-08
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.