Toán tử không thể == được áp dụng cho các loại chung trong C #?


326

Theo tài liệu của ==nhà điều hành trong MSDN ,

Đối với các loại giá trị được xác định trước, toán tử đẳng thức (==) trả về true nếu các giá trị của toán hạng của nó bằng nhau, sai khác. Đối với các kiểu tham chiếu khác với chuỗi, == trả về true nếu hai toán hạng của nó tham chiếu đến cùng một đối tượng. Đối với loại chuỗi, == so sánh các giá trị của chuỗi. Các loại giá trị do người dùng xác định có thể quá tải toán tử == (xem toán tử). Vì vậy, các loại tham chiếu do người dùng định nghĩa, mặc dù theo mặc định == hoạt động như được mô tả ở trên cho cả các loại tham chiếu được xác định trước và do người dùng xác định.

Vậy tại sao đoạn mã này không biên dịch được?

bool Compare<T>(T x, T y) { return x == y; }

Tôi gặp lỗi Toán tử '==' không thể áp dụng cho toán hạng loại 'T' và 'T' . Tôi tự hỏi tại sao, theo như tôi hiểu ==toán tử được xác định trước cho tất cả các loại?

Chỉnh sửa: Cảm ơn mọi người. Lúc đầu tôi không nhận thấy rằng tuyên bố chỉ nói về các loại tham chiếu. Tôi cũng nghĩ rằng so sánh từng bit được cung cấp cho tất cả các loại giá trị, mà bây giờ tôi biết là không chính xác.

Nhưng, trong trường hợp tôi đang sử dụng một loại tham chiếu, thì ==toán tử sẽ sử dụng so sánh tham chiếu được xác định trước hay nó sẽ sử dụng phiên bản quá tải của toán tử nếu một kiểu được xác định?

Chỉnh sửa 2: Thông qua thử nghiệm và lỗi, chúng tôi đã biết rằng ==toán tử sẽ sử dụng so sánh tham chiếu được xác định trước khi sử dụng loại chung không hạn chế. Trên thực tế, trình biên dịch sẽ sử dụng phương thức tốt nhất mà nó có thể tìm thấy cho đối số loại bị hạn chế, nhưng sẽ không tìm kiếm gì thêm. Ví dụ: mã bên dưới sẽ luôn in true, ngay cả khi Test.test<B>(new B(), new B())được gọi:

class A { public static bool operator==(A x, A y) { return true; } }
class B : A { public static bool operator==(B x, B y) { return false; } }
class Test { void test<T>(T a, T b) where T : A { Console.WriteLine(a == b); } }

Xem câu trả lời của tôi một lần nữa cho câu trả lời cho câu hỏi tiếp theo của bạn.
Giovanni Galbo

Có thể hữu ích để hiểu rằng ngay cả khi không có khái quát, có một số loại ==không được phép giữa hai toán hạng cùng loại. Điều này đúng với structcác loại (ngoại trừ các loại "được xác định trước") không quá tải operator ==. Một ví dụ đơn giản, hãy thử điều này:var map = typeof(string).GetInterfaceMap(typeof(ICloneable)); Console.WriteLine(map == map); /* compile-time error */
Jeppe Stig Nielsen

Tiếp tục bình luận cũ của riêng tôi. Ví dụ (xem chủ đề khác ), với var kvp1 = new KeyValuePair<int, int>(); var kvp2 = kvp1;, sau đó bạn không thể kiểm tra kvp1 == kvp2KeyValuePair<,>là cấu trúc, nó không phải là loại được xác định trước C # và nó không quá tải operator ==. Tuy nhiên, một ví dụ được đưa ra var li = new List<int>(); var e1 = li.GetEnumerator(); var e2 = e1;mà bạn không thể làm được e1 == e2(ở đây chúng ta có cấu trúc lồng nhau List<>.Enumerator(được gọi "List`1+Enumerator[T]"bởi bộ thực thi) không quá tải ==).
Jeppe Stig Nielsen

RE: "Vậy tại sao đoạn mã này không biên dịch được?" - Er ... bởi vì bạn không thể trả lại booltừ void...
BrainSlugs83

1
@ BrainSlugs83 Cảm ơn bạn đã bắt được một con bọ 10 tuổi!
Hosam Aly

Câu trả lời:


143

"... theo mặc định == hành xử như được mô tả ở trên cho cả loại tham chiếu được xác định trước và do người dùng xác định."

Loại T không nhất thiết là loại tham chiếu, vì vậy trình biên dịch không thể đưa ra giả định đó.

Tuy nhiên, điều này sẽ biên dịch vì nó rõ ràng hơn:

    bool Compare<T>(T x, T y) where T : class
    {
        return x == y;
    }

Theo dõi câu hỏi bổ sung, "Nhưng, trong trường hợp tôi đang sử dụng loại tham chiếu, thì toán tử == sẽ sử dụng so sánh tham chiếu được xác định trước hay nó sẽ sử dụng phiên bản quá tải của toán tử nếu một loại được xác định?"

Tôi đã nghĩ rằng == trên Generics sẽ sử dụng phiên bản quá tải, nhưng thử nghiệm sau đây cho thấy điều khác. Thú vị ... Tôi muốn biết tại sao! Nếu ai đó biết xin hãy chia sẻ.

namespace TestProject
{
 class Program
 {
    static void Main(string[] args)
    {
        Test a = new Test();
        Test b = new Test();

        Console.WriteLine("Inline:");
        bool x = a == b;
        Console.WriteLine("Generic:");
        Compare<Test>(a, b);

    }


    static bool Compare<T>(T x, T y) where T : class
    {
        return x == y;
    }
 }

 class Test
 {
    public static bool operator ==(Test a, Test b)
    {
        Console.WriteLine("Overloaded == called");
        return a.Equals(b);
    }

    public static bool operator !=(Test a, Test b)
    {
        Console.WriteLine("Overloaded != called");
        return a.Equals(b);
    }
  }
}

Đầu ra

Nội tuyến: Quá tải == được gọi

Chung:

Bấm phím bất kỳ để tiếp tục . . .

Theo dõi 2

Tôi muốn chỉ ra rằng thay đổi phương pháp so sánh của tôi thành

    static bool Compare<T>(T x, T y) where T : Test
    {
        return x == y;
    }

làm cho toán tử == bị quá tải được gọi. Tôi đoán mà không chỉ định loại (như một nơi ), trình biên dịch không thể suy luận rằng nó nên sử dụng toán tử quá tải ... mặc dù tôi nghĩ rằng nó sẽ có đủ thông tin để đưa ra quyết định ngay cả khi không chỉ định loại.


Cảm ơn. Tôi đã không nhận thấy rằng tuyên bố chỉ về các loại tham chiếu.
Hosam Aly

4
Re: Follow Up 2: Trên thực tế trình biên dịch sẽ liên kết nó với phương thức tốt nhất mà nó tìm thấy, đó là trong trường hợp này Test.op_Equal. Nhưng nếu bạn có một lớp xuất phát từ Test và ghi đè toán tử, thì toán tử của Test vẫn sẽ được gọi.
Hosam Aly

4
Tôi thực hành tốt mà tôi muốn chỉ ra là bạn phải luôn luôn thực hiện so sánh thực tế bên trong một Equalsphương thức được ghi đè (không phải trong ==toán tử).
jpbochi

11
Quá tải giải quyết xảy ra thời gian biên dịch. Vì vậy, khi chúng ta có ==giữa các loại chung TT, quá tải tốt nhất được tìm thấy, dựa trên những ràng buộc nào được thực hiện T(có một quy tắc đặc biệt là nó sẽ không bao giờ đóng hộp loại giá trị cho điều này (sẽ cho kết quả vô nghĩa), do đó phải có một số ràng buộc đảm bảo đó là một loại tham chiếu). Trong Theo dõi 2 của bạn , nếu bạn đến với DerivedTestcác đối tượng và DerivedTestxuất phát từ Testnhưng giới thiệu một tình trạng quá tải mới ==, bạn sẽ lại gặp "vấn đề". Quá tải được gọi là "bị đốt cháy" vào IL tại thời điểm biên dịch.
Jeppe Stig Nielsen

1
wierdly điều này dường như hoạt động cho các loại tham chiếu chung (trong đó bạn mong muốn sự so sánh này là bằng đẳng thức tham chiếu) nhưng đối với các chuỗi dường như cũng sử dụng đẳng thức tham chiếu - vì vậy bạn có thể kết thúc so sánh 2 chuỗi giống hệt nhau và có == (khi ở một phương thức chung với ràng buộc lớp) nói rằng chúng khác nhau.
JonnyRaa

291

Như những người khác đã nói, nó sẽ chỉ hoạt động khi T bị ràng buộc là một kiểu tham chiếu. Không có bất kỳ ràng buộc nào, bạn có thể so sánh với null, nhưng chỉ null - và so sánh đó sẽ luôn sai đối với các loại giá trị không null.

Thay vì gọi Equals, tốt hơn là sử dụng một IComparer<T>- và nếu bạn không có thêm thông tin, EqualityComparer<T>.Defaultlà một lựa chọn tốt:

public bool Compare<T>(T x, T y)
{
    return EqualityComparer<T>.Default.Equals(x, y);
}

Ngoài bất cứ điều gì khác, điều này tránh đấm bốc / đúc.


Cảm ơn. Tôi đã cố gắng viết một lớp trình bao bọc đơn giản, vì vậy tôi chỉ muốn ủy thác thao tác cho thành viên được bao bọc thực tế. Nhưng biết về EqualityComparer <T> .Default chắc chắn đã tăng thêm giá trị cho tôi. :)
Hosam Aly

Bỏ qua một bên, Jon; bạn có thể muốn lưu ý bình luận re pobox vs yoda trên bài viết của tôi.
Marc Gravell

4
Mẹo hay khi sử dụng EqualityComparer <T>
chakrit

1
+1 để chỉ ra rằng nó có thể so sánh với null và đối với loại giá trị không null, nó sẽ luôn sai
Jalal cho biết

@BlueRaja: Có, bởi vì có những quy tắc đặc biệt để so sánh với chữ null. Do đó "không có bất kỳ ràng buộc nào, bạn có thể so sánh với null, nhưng chỉ null". Đó là trong câu trả lời rồi. Vì vậy, tại sao chính xác điều này có thể không chính xác?
Jon Skeet

41

Nói chung, EqualityComparer<T>.Default.Equalsnên thực hiện công việc với bất cứ điều gì thực hiện IEquatable<T>, hoặc có Equalstriển khai hợp lý .

Tuy nhiên, nếu, ==Equalsđược triển khai khác nhau vì một số lý do, thì công việc của tôi về các toán tử chung sẽ hữu ích; nó hỗ trợ các phiên bản toán tử của (trong số những người khác):

  • Bằng nhau (giá trị T1, giá trị T2)
  • NotEqual (T value1, T value2)
  • GreaterThan (T value1, T value2)
  • LessThan (T value1, T value2)
  • GreaterThanOrEqual (T value1, T value2)
  • LessThanOrEqual (T value1, T value2)

Thư viện rất thú vị! :) (? Side lưu ý: tháng tôi đề nghị sử dụng các liên kết đến www.yoda.arachsys.com, bởi vì một POBox đã bị chặn bởi tường lửa ở nơi làm việc của tôi Có thể là người khác có thể đối mặt với những vấn đề tương tự.)
Hosam Aly

Ý tưởng là pobox.com/~skeet sẽ luôn trỏ đến trang web của tôi - ngay cả khi nó di chuyển ở nơi khác. Tôi có xu hướng đăng liên kết qua pobox.com vì lợi ích của hậu thế - nhưng hiện tại bạn có thể thay thế yoda.arachsys.com.
Jon Skeet

Vấn đề với pobox.com là dịch vụ email dựa trên web (hay tường lửa của công ty nói), vì vậy nó bị chặn. Đó là lý do tại sao tôi không thể theo liên kết của nó.
Hosam Aly

"Tuy nhiên, nếu == và Equals được triển khai khác nhau vì một số lý do" - Holy hút thuốc! Thật là một tuy nhiên! Có lẽ tôi chỉ cần nhìn thấy một trường hợp sử dụng ngược lại, nhưng một thư viện với sự khác biệt về ngữ nghĩa có thể sẽ gặp vấn đề lớn hơn là rắc rối với thuốc generic.
Edward Brey

@EdwardBrey bạn không sai; thật tuyệt nếu trình biên dịch có thể thực thi điều đó, nhưng ...
Marc Gravell

31

Rất nhiều câu trả lời, và không một câu trả lời nào giải thích TẠI SAO? (mà Giovanni hỏi rõ ràng) ...

.NET generic không hoạt động như các mẫu C ++. Trong các mẫu C ++, độ phân giải quá tải xảy ra sau khi biết các tham số mẫu thực tế.

Trong .NET generic (bao gồm C #), độ phân giải quá tải xảy ra mà không biết các tham số chung thực tế. Thông tin duy nhất mà trình biên dịch có thể sử dụng để chọn hàm cần gọi đến từ các ràng buộc kiểu trên các tham số chung.


2
nhưng tại sao trình biên dịch không thể coi chúng là một đối tượng chung? sau khi tất cả các ==công việc cho tất cả các loại có thể là loại tham chiếu hoặc loại giá trị. Đó phải là câu hỏi mà tôi không nghĩ bạn đã trả lời.
nawfal

4
@nawfal: Trên thực tế không, ==không hoạt động cho tất cả các loại giá trị. Quan trọng hơn, nó không có cùng ý nghĩa với tất cả các loại, vì vậy trình biên dịch không biết phải làm gì với nó.
Ben Voigt

1
Ben, vâng, tôi đã bỏ lỡ các cấu trúc tùy chỉnh mà chúng ta có thể tạo mà không cần bất kỳ ==. Bạn có thể đưa phần đó vào câu trả lời của bạn không vì tôi đoán đó là điểm chính ở đây
nawfal

12

Trình biên dịch không thể biết T không thể là một cấu trúc (kiểu giá trị). Vì vậy, bạn phải nói với nó rằng nó chỉ có thể là loại tham chiếu tôi nghĩ:

bool Compare<T>(T x, T y) where T : class { return x == y; }

Đó là bởi vì nếu T có thể là một loại giá trị, có thể có trường hợp x == ysẽ bị hình thành - trong trường hợp khi một loại không có toán tử == được xác định. Điều tương tự sẽ xảy ra cho điều này rõ ràng hơn:

void CallFoo<T>(T x) { x.foo(); }

Điều đó cũng thất bại, bởi vì bạn có thể vượt qua một loại T sẽ không có chức năng foo. C # buộc bạn phải đảm bảo tất cả các loại có thể luôn có chức năng foo. Điều đó được thực hiện bởi mệnh đề where.


1
Cảm ơn bạn đã làm rõ. Tôi không biết rằng các loại giá trị không hỗ trợ toán tử == ngoài hộp.
Hosam Aly

1
Ôi, tôi đã thử nghiệm với gmcs (mono), và nó luôn so sánh các tài liệu tham khảo. (tức là nó không sử dụng toán tử được xác định tùy chọn == cho T)
Johannes Schaub - litb

Có một cảnh báo với giải pháp này: toán tử == không thể bị quá tải; xem câu hỏi StackOverflow này .
Dimitri C.

8

Dường như không có ràng buộc lớp:

bool Compare<T> (T x, T y) where T: class
{
    return x == y;
}

Người ta nên nhận ra rằng trong khi classràng buộc Equalstrong ==toán tử kế thừa từ Object.Equals, trong khi đó của cấu trúc ghi đè ValueType.Equals.

Lưu ý rằng:

bool Compare<T> (T x, T y) where T: struct
{
    return x == y;
}

cũng đưa ra lỗi trình biên dịch tương tự.

Vẫn chưa hiểu tại sao việc so sánh toán tử đẳng thức loại giá trị bị từ chối bởi trình biên dịch. Tôi thực sự biết rằng điều này hoạt động:

bool Compare<T> (T x, T y)
{
    return x.Equals(y);
}

bạn biết tôi là một tổng số c # noob. nhưng tôi nghĩ nó thất bại vì trình biên dịch không biết phải làm gì. vì T chưa được biết đến, những gì được thực hiện tùy thuộc vào loại T nếu loại giá trị sẽ được cho phép. đối với tài liệu tham khảo, các tài liệu tham khảo chỉ được so sánh bất kể T. nếu bạn làm .Equals, sau đó .Equal chỉ được gọi.
Julian Schaub - litb

nhưng nếu bạn thực hiện == trên một loại giá trị, loại giá trị không cần phải thực hiện toán tử đó.
Julian Schaub - litb

Điều đó có ý nghĩa, litb :) Có thể các cấu trúc do người dùng định nghĩa không quá tải ==, do đó trình biên dịch thất bại.
Jon Limjap

2
Phương pháp so sánh đầu tiên không sử dụng Object.Equalsmà thay vào đó kiểm tra sự bằng nhau tham chiếu. Ví dụ, Compare("0", 0.ToString())sẽ trả về false, vì các đối số sẽ là tham chiếu đến các chuỗi riêng biệt, cả hai đều có số 0 là ký tự duy nhất của chúng.
supercat

1
Gotcha nhỏ trên cái cuối cùng đó - bạn đã không giới hạn nó trong các cấu trúc, vì vậy điều đó NullReferenceExceptioncó thể xảy ra.
Flynn1179

6

Trong trường hợp của tôi, tôi muốn kiểm tra đơn vị toán tử đẳng thức. Tôi cần gọi mã theo các toán tử đẳng thức mà không đặt rõ ràng loại chung. Các lời khuyên cho EqualityComparerkhông hữu ích như phương thức EqualityComparerđược gọi Equalsnhưng không phải là toán tử đẳng thức.

Đây là cách tôi đã làm việc này với các loại chung bằng cách xây dựng một LINQ. Nó gọi đúng mã cho ==!=toán tử:

/// <summary>
/// Gets the result of "a == b"
/// </summary>
public bool GetEqualityOperatorResult<T>(T a, T b)
{
    // declare the parameters
    var paramA = Expression.Parameter(typeof(T), nameof(a));
    var paramB = Expression.Parameter(typeof(T), nameof(b));
    // get equality expression for the parameters
    var body = Expression.Equal(paramA, paramB);
    // compile it
    var invokeEqualityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
    // call it
    return invokeEqualityOperator(a, b);
}

/// <summary>
/// Gets the result of "a =! b"
/// </summary>
public bool GetInequalityOperatorResult<T>(T a, T b)
{
    // declare the parameters
    var paramA = Expression.Parameter(typeof(T), nameof(a));
    var paramB = Expression.Parameter(typeof(T), nameof(b));
    // get equality expression for the parameters
    var body = Expression.NotEqual(paramA, paramB);
    // compile it
    var invokeInequalityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
    // call it
    return invokeInequalityOperator(a, b);
}

4

Có một mục Kết nối MSDN cho điều này ở đây

Câu trả lời của Alex Turner bắt đầu bằng:

Thật không may, hành vi này là theo thiết kế và không có một giải pháp dễ dàng nào cho phép sử dụng == với các tham số loại có thể chứa các loại giá trị.


4

Nếu bạn muốn đảm bảo các toán tử của loại tùy chỉnh của bạn được gọi, bạn có thể làm như vậy thông qua sự phản chiếu. Chỉ cần lấy loại bằng tham số chung của bạn và truy xuất MethodInfo cho toán tử mong muốn (ví dụ: op_Equality, op_Inequality, op_LessThan ...).

var methodInfo = typeof (T).GetMethod("op_Equality", 
                             BindingFlags.Static | BindingFlags.Public);    

Sau đó thực thi toán tử bằng phương thức Invoke của MethodInfo và truyền vào các đối tượng làm tham số.

var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2});

Điều này sẽ gọi toán tử quá tải của bạn và không phải là toán tử được xác định bởi các ràng buộc được áp dụng trên tham số chung. Có thể không thực tế, nhưng có thể có ích để kiểm tra đơn vị toán tử của bạn khi sử dụng lớp cơ sở chung có chứa một vài bài kiểm tra.


3

Tôi đã viết các chức năng sau đây nhìn vào msDN mới nhất. Nó có thể dễ dàng so sánh hai đối tượng xy:

static bool IsLessThan(T x, T y) 
{
    return ((IComparable)(x)).CompareTo(y) <= 0;
}

4
Bạn có thể thoát khỏi return ((IComparable)(x)).CompareTo(y) <= 0;
booleans

1

bool Compare(T x, T y) where T : class { return x == y; }

Ở trên sẽ hoạt động vì == được quan tâm trong trường hợp các loại tham chiếu do người dùng xác định.
Trong trường hợp các loại giá trị, == có thể bị ghi đè. Trong trường hợp đó, "! =" Cũng nên được xác định.

Tôi nghĩ đó có thể là lý do, nó không cho phép so sánh chung bằng cách sử dụng "==".


2
Cảm ơn. Tôi tin rằng các loại tham chiếu cũng có thể ghi đè toán tử quá. Nhưng lý do thất bại hiện rõ ràng.
Hosam Aly

1
==thông báo được sử dụng cho hai nhà khai thác khác nhau. Nếu đối với các loại toán hạng đã cho tồn tại sự quá tải tương thích của toán tử đẳng thức, thì quá tải đó sẽ được sử dụng. Mặt khác, nếu cả hai toán hạng là loại tham chiếu tương thích với nhau, so sánh tham chiếu sẽ được sử dụng. Lưu ý rằng trong Comparephương thức trên, trình biên dịch không thể nói rằng nghĩa thứ nhất được áp dụng, nhưng có thể cho biết nghĩa thứ hai được áp dụng, do đó, ==mã thông báo sẽ sử dụng nghĩa sau ngay cả khi Tquá tải toán tử kiểm tra đẳng thức (ví dụ: nếu loại này String) .
supercat

0

Các .Equals()công việc cho tôi trong khi TKeylà một loại chung chung.

public virtual TOutputDto GetOne(TKey id)
{
    var entity =
        _unitOfWork.BaseRepository
            .FindByCondition(x => 
                !x.IsDelete && 
                x.Id.Equals(id))
            .SingleOrDefault();


    // ...
}

Đó là x.Id.Equals, không phải id.Equals. Có lẽ, trình biên dịch biết một cái gì đó về loại x.
Hosam Aly
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.