Tidak bisakah operator == diterapkan pada tipe umum dalam C #?

326

Menurut dokumentasi ==operator di MSDN ,

Untuk tipe nilai yang telah ditentukan, operator persamaan (==) mengembalikan true jika nilai operandnya sama, salah jika tidak. Untuk tipe referensi selain string, == mengembalikan true jika kedua operandnya merujuk ke objek yang sama. Untuk tipe string, == membandingkan nilai string. Jenis nilai yang ditentukan pengguna dapat membebani operator == (lihat operator). Demikian juga tipe referensi yang ditentukan pengguna, meskipun secara default == berperilaku seperti yang dijelaskan di atas untuk tipe referensi yang ditentukan sebelumnya dan yang ditentukan pengguna.

Jadi mengapa cuplikan kode ini gagal dikompilasi?

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

Saya mendapatkan kesalahan Operator '==' tidak dapat diterapkan ke operan tipe 'T' dan 'T' . Saya bertanya-tanya mengapa, karena sejauh yang saya mengerti ==operator sudah ditentukan untuk semua jenis?

Sunting: Terima kasih, semuanya. Awalnya saya tidak memperhatikan bahwa pernyataan itu hanya tentang tipe referensi. Saya juga berpikir bahwa perbandingan sedikit demi sedikit disediakan untuk semua tipe nilai, yang sekarang saya tahu tidak benar.

Tetapi, jika saya menggunakan tipe referensi, apakah ==operator akan menggunakan perbandingan referensi yang sudah ditentukan sebelumnya, atau akankah menggunakan versi overload dari operator jika suatu tipe didefinisikan?

Sunting 2: Melalui percobaan dan kesalahan, kami mengetahui bahwa ==operator akan menggunakan perbandingan referensi yang telah ditentukan saat menggunakan jenis generik yang tidak dibatasi. Sebenarnya, kompiler akan menggunakan metode terbaik yang dapat ditemukan untuk argumen tipe terbatas, tetapi tidak akan mencari lagi. Misalnya, kode di bawah ini akan selalu dicetak true, bahkan ketika Test.test<B>(new B(), new B())dipanggil:

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); } }
Hosam Aly
sumber
Lihat jawaban saya lagi untuk jawaban atas pertanyaan tindak lanjut Anda.
Giovanni Galbo
Mungkin berguna untuk memahami bahwa bahkan tanpa obat generik, ada beberapa jenis yang ==tidak diizinkan antara dua operan dari jenis yang sama. Ini berlaku untuk structtipe (kecuali tipe "yang ditentukan") yang tidak membebani operator ==. Sebagai contoh sederhana, coba ini:var map = typeof(string).GetInterfaceMap(typeof(ICloneable)); Console.WriteLine(map == map); /* compile-time error */
Jeppe Stig Nielsen
Melanjutkan komentar lamaku sendiri. Sebagai contoh (lihat utas lainnya ), dengan var kvp1 = new KeyValuePair<int, int>(); var kvp2 = kvp1;, maka Anda tidak dapat memeriksa kvp1 == kvp2karena KeyValuePair<,>merupakan sebuah struct, itu bukan tipe yang ditentukan # C, dan itu tidak membebani operator ==. Namun contoh diberikan var li = new List<int>(); var e1 = li.GetEnumerator(); var e2 = e1;dengan mana Anda tidak dapat melakukan e1 == e2(di sini kami memiliki struct bersarang List<>.Enumerator(disebut "List`1+Enumerator[T]"dengan runtime) yang tidak kelebihan beban ==).
Jeppe Stig Nielsen
RE: "Jadi mengapa cuplikan kode ini gagal dikompilasi?" - Er ... karena Anda tidak dapat mengembalikan booldari void...
BrainSlugs83
1
@ BrainSlugs83 Terima kasih telah menangkap bug berusia 10 tahun!
Hosam Aly

Jawaban:

143

"... secara default == berperilaku seperti dijelaskan di atas untuk tipe referensi yang ditentukan sebelumnya dan yang ditentukan pengguna."

Tipe T belum tentu merupakan tipe referensi, jadi kompiler tidak dapat membuat asumsi itu.

Namun, ini akan dikompilasi karena lebih eksplisit:

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

Tindak lanjuti pertanyaan tambahan, "Tapi, kalau-kalau saya menggunakan tipe referensi, apakah operator == akan menggunakan perbandingan referensi yang telah ditentukan, atau akankah ia menggunakan versi overload dari operator jika suatu tipe mendefinisikannya?"

Saya akan berpikir bahwa == pada Generik akan menggunakan versi overload, tetapi tes berikut menunjukkan sebaliknya. Menarik ... Saya ingin tahu kenapa! Jika ada yang tahu, silakan berbagi.

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);
    }
  }
}

Keluaran

Inline: Kelebihan == dipanggil

Umum:

Tekan tombol apa saja untuk melanjutkan . . .

Tindak Lanjut 2

Saya ingin menunjukkan bahwa mengubah metode perbandingan ke

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

menyebabkan operator == kelebihan beban dipanggil. Saya kira tanpa menentukan jenisnya (sebagai mana ), kompiler tidak dapat menyimpulkan bahwa ia harus menggunakan operator yang kelebihan beban ... meskipun saya pikir itu akan memiliki informasi yang cukup untuk membuat keputusan itu bahkan tanpa menentukan jenisnya.

Giovanni Galbo
sumber
Terima kasih. Saya tidak memperhatikan bahwa pernyataan itu hanya tentang tipe referensi.
Hosam Aly
4
Re: Follow Up 2: Sebenarnya kompiler akan menautkannya metode terbaik yang ditemukannya, yaitu dalam hal ini Test.op_Equal. Tetapi jika Anda memiliki kelas yang berasal dari Test dan menimpa operator, maka operator Test masih akan dipanggil.
Hosam Aly
4
Saya praktik yang baik yang ingin saya tunjukkan adalah bahwa Anda harus selalu melakukan perbandingan aktual di dalam Equalsmetode yang diganti (bukan di ==operator).
jpbochi
11
Resolusi kelebihan terjadi saat kompilasi. Jadi, ketika kita memiliki ==antara tipe generik Tdan T, kelebihan beban terbaik ditemukan, mengingat kendala apa yang dipikul T(ada aturan khusus yang tidak akan pernah mengotakkan tipe nilai untuk ini (yang akan memberikan hasil yang tidak berarti), maka harus ada beberapa kendala menjamin itu adalah tipe referensi). Dalam Follow Up 2 Anda , jika Anda datang dengan DerivedTestobjek, dan DerivedTestberasal dari Testtetapi memperkenalkan kelebihan baru ==, Anda akan memiliki "masalah" lagi. Yang kelebihan beban disebut, "dibakar" ke IL pada saat kompilasi.
Jeppe Stig Nielsen
1
anehnya ini tampaknya bekerja untuk jenis referensi umum (di mana Anda akan mengharapkan perbandingan ini pada kesetaraan referensi) tetapi untuk string tampaknya juga menggunakan kesetaraan referensi - sehingga Anda akhirnya dapat membandingkan 2 string identik dan memiliki == (ketika dalam suatu metode generik dengan batasan kelas) mengatakan mereka berbeda.
JonnyRaa
292

Seperti yang dikatakan orang lain, ini hanya akan berfungsi ketika T dibatasi menjadi tipe referensi. Tanpa kendala apa pun, Anda dapat membandingkan dengan nol, tetapi hanya nol - dan perbandingan itu akan selalu salah untuk tipe nilai yang tidak dapat dibatalkan.

Alih-alih memanggil Equals, lebih baik menggunakan IComparer<T>- dan jika Anda tidak memiliki informasi lagi, EqualityComparer<T>.Defaultadalah pilihan yang baik:

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

Selain dari hal lain, ini menghindari tinju / casting.

Jon Skeet
sumber
Terima kasih. Saya mencoba menulis kelas pembungkus sederhana, jadi saya hanya ingin mendelegasikan operasi kepada anggota yang dibungkus sebenarnya. Tetapi mengetahui tentang EqualityComparer <T>. Kerugian tentu saja menambah nilai bagi saya. :)
Hosam Aly
Di samping minor, Jon; Anda mungkin ingin mencatat komentar re pobox vs yoda di posting saya.
Marc Gravell
4
Tip yang bagus tentang penggunaan EqualityComparer <T>
chakrit
1
+1 untuk menunjukkan bahwa hal itu dapat dibandingkan dengan nol dan untuk jenis nilai yang tidak dapat dibatalkan akan selalu salah
Jalal Said
@ BlueRaja: Ya, karena ada aturan khusus untuk perbandingan dengan nol literal. Karenanya "tanpa kendala apa pun, Anda dapat membandingkan dengan nol, tetapi hanya nol". Sudah ada dalam jawaban. Jadi, mengapa sebenarnya ini tidak benar?
Jon Skeet
41

Secara umum, EqualityComparer<T>.Default.Equalsharus melakukan pekerjaan dengan apa pun yang mengimplementasikan IEquatable<T>, atau yang memiliki Equalsimplementasi yang masuk akal .

Namun, jika ==dan Equalsdiimplementasikan secara berbeda karena alasan tertentu, maka pekerjaan saya pada operator generik akan berguna; mendukung versi operator (antara lain):

  • Equal (Nilai T1, Nilai T2)
  • NotEqual (T value1, T value2)
  • GreaterThan (Nilai T1, Nilai T2)
  • LessThan (Nilai T1, Nilai T2)
  • GreaterThanOrEqual (Nilai T1, Nilai T2)
  • LessThanOrEqual (Nilai T1, Nilai T2)
Marc Gravell
sumber
Perpustakaan yang sangat menarik! :) (Catatan: Bolehkah saya menyarankan menggunakan tautan ke www.yoda.arachsys.com, karena pobox diblokir oleh firewall di tempat kerja saya? Mungkin saja orang lain menghadapi masalah yang sama.)
Hosam Aly
Idenya adalah pobox.com/~skeet akan selalu mengarah ke situs web saya - bahkan jika itu pindah ke tempat lain. Saya cenderung memposting tautan melalui pobox.com demi keturunan - tetapi saat ini Anda dapat mengganti yoda.arachsys.com.
Jon Skeet
Masalah dengan pobox.com adalah layanan e-mail berbasis web (atau begitulah kata firewall perusahaan), sehingga diblokir. Itu sebabnya saya tidak bisa mengikuti tautannya.
Hosam Aly
"Namun, jika == dan Persamaan diterapkan secara berbeda karena beberapa alasan" - Holy smokes! Namun apa yang luar biasa! Mungkin saya hanya perlu melihat use case yang bertentangan, tetapi perpustakaan dengan semantik yang sama dengan semantik kemungkinan akan mengalami masalah yang lebih besar daripada masalah dengan obat generik.
Edward Brey
@ EdwardBrey Anda tidak salah; alangkah baiknya jika kompiler dapat menegakkan itu, tapi ...
Marc Gravell
31

Begitu banyak jawaban, dan tidak satu pun yang menjelaskan MENGAPA? (yang diminta secara eksplisit oleh Giovanni) ...

.NET generics tidak bertindak seperti template C ++. Di template C ++, resolusi kelebihan terjadi setelah parameter template aktual diketahui.

Dalam .NET generics (termasuk C #), resolusi kelebihan terjadi tanpa mengetahui parameter generik yang sebenarnya. Satu-satunya informasi yang dapat digunakan kompilator untuk memilih fungsi untuk memanggil berasal dari batasan tipe pada parameter generik.

Ben Voigt
sumber
2
tetapi mengapa kompiler tidak dapat memperlakukan mereka sebagai objek generik? setelah semua ==bekerja untuk semua jenis baik itu jenis referensi atau tipe nilai. Itu seharusnya menjadi pertanyaan yang saya pikir Anda tidak menjawab.
nawfal
4
@nawfal: Sebenarnya tidak, ==tidak berfungsi untuk semua tipe nilai. Lebih penting lagi, itu tidak memiliki arti yang sama untuk semua jenis, sehingga kompiler tidak tahu apa yang harus dilakukan dengannya.
Ben Voigt
1
Ben, oh ya saya ketinggalan struct kustom yang bisa kita buat tanpa ==. Bisakah Anda memasukkan bagian itu juga dalam jawaban Anda karena saya kira itulah poin utama di sini
nawfal
12

Kompilasi tidak dapat mengetahui T tidak dapat berupa struct (tipe nilai). Jadi Anda harus mengatakan itu hanya bisa dari jenis referensi, saya pikir:

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

Itu karena jika T bisa menjadi tipe nilai, mungkin ada kasus di mana x == yakan terbentuk buruk - dalam kasus ketika tipe tidak memiliki operator == didefinisikan. Hal yang sama akan terjadi untuk ini yang lebih jelas:

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

Itu gagal juga, karena Anda bisa melewati tipe T yang tidak akan memiliki fungsi foo. C # memaksa Anda untuk memastikan semua tipe yang mungkin selalu memiliki fungsi foo. Itu dilakukan oleh klausa mana.

Johannes Schaub - litb
sumber
1
Terimakasih atas klarifikasinya. Saya tidak tahu bahwa tipe nilai tidak mendukung operator == di luar kotak.
Hosam Aly
1
Hosam, saya diuji dengan gmcs (mono), dan selalu membandingkan referensi. (Yaitu tidak menggunakan operator opsional == untuk T)
Johannes Schaub - litb
Ada satu peringatan dengan solusi ini: operator == tidak dapat kelebihan beban; lihat pertanyaan StackOverflow ini .
Dimitri C.
8

Tampaknya tanpa batasan kelas:

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

Orang harus menyadari bahwa sementara classdibatasi Equalsdalam ==operator mewarisi dari Object.Equals, sedangkan yang menimpa struct ValueType.Equals.

Perhatikan bahwa:

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

juga memberikan kesalahan kompiler yang sama.

Sampai sekarang saya tidak mengerti mengapa memiliki perbandingan operator kesetaraan tipe nilai ditolak oleh kompiler. Saya tahu pasti, bahwa ini bekerja:

bool Compare<T> (T x, T y)
{
    return x.Equals(y);
}
Jon Limjap
sumber
kamu tahu aku total c # noob. tetapi saya pikir itu gagal karena kompiler tidak tahu apa yang harus dilakukan. karena T belum diketahui, apa yang dilakukan tergantung pada tipe T jika tipe nilai diizinkan. untuk referensi, referensi hanya dibandingkan terlepas dari T. jika Anda melakukannya. Sama, lalu. Sama hanya dipanggil.
Johannes Schaub - litb
tetapi jika Anda == pada tipe nilai, tipe nilai tidak harus mengimplementasikan operator itu.
Johannes Schaub - litb
Itu masuk akal, litb :) Ada kemungkinan bahwa struct yang ditentukan pengguna tidak overload ==, maka kompiler gagal.
Jon Limjap
2
Metode membandingkan pertama tidak menggunakan Object.Equalsmelainkan menguji kesetaraan referensi. Misalnya, Compare("0", 0.ToString())akan menghasilkan false, karena argumen akan menjadi referensi untuk string yang berbeda, keduanya memiliki nol sebagai satu-satunya karakter mereka.
supercat
1
Gotcha kecil pada yang terakhir-Anda belum membatasi itu untuk struct, sehingga NullReferenceExceptionbisa terjadi.
Flynn1179
6

Baik dalam kasus saya, saya ingin menguji unit operator kesetaraan. Saya perlu memanggil kode di bawah operator kesetaraan tanpa secara eksplisit menetapkan jenis generik. Saran untuk EqualityComparertidak membantu EqualityComparerdisebut Equalsmetode tetapi bukan operator kesetaraan.

Berikut adalah cara kerja ini dengan tipe generik dengan membangun a LINQ. Itu memanggil kode yang tepat untuk ==dan !=operator:

/// <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);
}
U. Bulle
sumber
4

Ada entri MSDN Connect untuk ini di sini

Balasan Alex Turner dimulai dengan:

Sayangnya, perilaku ini dirancang dan tidak ada solusi mudah untuk memungkinkan penggunaan == dengan parameter tipe yang mungkin berisi tipe nilai.

Recep
sumber
4

Jika Anda ingin memastikan operator jenis kustom Anda dipanggil, Anda dapat melakukannya melalui refleksi. Dapatkan saja tipe menggunakan parameter generik Anda dan ambil MethodInfo untuk operator yang diinginkan (mis. Op_Equality, op_Inequality, op_LessThan ...).

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

Kemudian jalankan operator menggunakan metode Invoke MethodInfo's dan meneruskan objek sebagai parameter.

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

Ini akan memanggil operator kelebihan beban Anda dan bukan yang ditentukan oleh kendala yang diterapkan pada parameter generik. Mungkin tidak praktis, tetapi bisa berguna untuk unit menguji operator Anda saat menggunakan kelas dasar generik yang berisi beberapa tes.

Christophe
sumber
3

Saya menulis fungsi berikut melihat msdn terbaru. Itu dapat dengan mudah membandingkan dua objek xdan y:

static bool IsLessThan(T x, T y) 
{
    return ((IComparable)(x)).CompareTo(y) <= 0;
}
Charlie
sumber
4
Anda dapat menyingkirkan boolean Anda dan menulisreturn ((IComparable)(x)).CompareTo(y) <= 0;
aloisdg pindah ke codidact.com
1

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

Hal di atas akan berfungsi karena == dirawat jika ada jenis referensi yang ditentukan pengguna.
Dalam hal tipe nilai, == dapat diganti. Dalam hal ini, "! =" Juga harus didefinisikan.

Saya pikir itu bisa menjadi alasannya, ia tidak mengizinkan perbandingan umum menggunakan "==".

shahkalpesh
sumber
2
Terima kasih. Saya percaya tipe referensi juga dapat menimpa operator. Namun alasan kegagalannya sekarang jelas.
Hosam Aly
1
The ==token digunakan untuk dua operator yang berbeda. Jika untuk jenis operan tertentu terdapat kelebihan yang kompatibel dari operator persamaan, kelebihan itu akan digunakan. Kalau tidak, jika kedua operan adalah tipe referensi yang kompatibel satu sama lain, perbandingan referensi akan digunakan. Perhatikan bahwa dalam Comparemetode di atas kompiler tidak dapat mengatakan bahwa makna pertama berlaku, tetapi dapat memberitahu arti kedua berlaku, sehingga ==token akan menggunakan yang terakhir bahkan jika Tmembebani operator pemeriksa kesetaraan (misalnya jika itu jenis String) .
supercat
0

The .Equals()bekerja untuk saya sementara TKeyadalah jenis generik.

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


    // ...
}
Masoud Darvishian
sumber
Itu x.Id.Equals, tidak id.Equals. Agaknya, kompiler tahu sesuatu tentang tipe x.
Hosam Aly