Operatör ==, C # içindeki genel türlere uygulanamaz mı?


326

Belgelerine göre ==operatör MSDN ,

Önceden tanımlanmış değer türleri için, işlenenlerinin değerleri eşitse eşitlik operatörü (==) true değerini, aksi takdirde false değerini döndürür. Dize dışındaki başvuru türleri için, iki işleneni aynı nesneye başvuruyorsa == true değerini döndürür. Dize türü için ==, dizelerin değerlerini karşılaştırır. Kullanıcı tanımlı değer türleri == operatörünü aşırı yükleyebilir (operatöre bakın). Varsayılan olarak == yukarıda tanımlanmış ve kullanıcı tanımlı referans türleri için yukarıda açıklandığı gibi davranmasına rağmen , kullanıcı tanımlı referans türleri de kullanılabilir.

Peki bu kod pasajı neden derlenemiyor?

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

'==' İşleci 'T' ve 'T' tipi işlenenlere uygulanamıyor hatasını alıyorum . Acaba neden anladığım kadarıyla ==operatör tüm tipler için önceden tanımlanmış?

Düzenleme: Herkese teşekkürler. İlk başta ifadenin sadece referans türleriyle ilgili olduğunu fark etmedim. Ayrıca, artık doğru olmadığını bildiğim tüm değer türleri için bit-bit karşılaştırmasının sağlandığını düşündüm .

Ancak, bir referans türü ==kullanmam durumunda , operatör önceden tanımlanmış referans karşılaştırmasını kullanır mı yoksa bir tür tanımlıysa, operatörün aşırı yüklenmiş sürümünü kullanır mı?

Düzenleme 2: Deneme yanılma yoluyla, ==operatörün sınırsız bir genel tip kullanırken önceden tanımlanmış referans karşılaştırmasını kullanacağını öğrendik . Aslında, derleyici kısıtlı tür argümanı için bulabileceği en iyi yöntemi kullanacaktır, ancak daha fazla bakmayacaktır. Örneğin, aşağıdaki kod çağrılsa truebile her zaman yazdırılır Test.test<B>(new B(), new B()):

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

Takip sorunuzun cevabı için cevabımı tekrar görün.
Giovanni Galbo

Jenerikler olmadan bile ==, aynı tipteki iki işlenen arasında buna izin verilmeyen bazı türlerin olduğunu anlamak yararlı olabilir . Bu, structaşırı yüklenmeyen türler için ("önceden tanımlanmış" türler hariç) geçerlidir operator ==. Basit bir örnek olarak, şunu deneyin:var map = typeof(string).GetInterfaceMap(typeof(ICloneable)); Console.WriteLine(map == map); /* compile-time error */
Jeppe Stig Nielsen

Kendi eski yorumuma devam ediyorum. Örneğin (bakınız diğer iplik ile) var kvp1 = new KeyValuePair<int, int>(); var kvp2 = kvp1;o zaman kontrol edemez, kvp1 == kvp2çünkü KeyValuePair<,>bu bir C # önceden tanımlanmış türü değil, bir yapı olduğunu ve aşırı değildir operator ==. Yine var li = new List<int>(); var e1 = li.GetEnumerator(); var e2 = e1;de yapamayacağınız bir örnek verilir e1 == e2(burada aşırı yüklenmeyen iç içe yapı List<>.Enumerator( "List`1+Enumerator[T]"çalışma zamanı tarafından çağrılır ) var ==).
Jeppe Stig Nielsen

RE: "Peki bu kod snippet'i neden derlenemiyor?" - Şey ... Eğer bir döndüremez çünkü boolbir mesafede void...
BrainSlugs83

1
@ BrainSlugs83 10 yaşındaki bir hatayı yakaladığınız için teşekkür ederiz!
Hosam Aly

Yanıtlar:


143

"... varsayılan olarak ==, hem önceden tanımlanmış hem de kullanıcı tanımlı referans türleri için yukarıda açıklandığı şekilde davranır."

T tipi mutlaka bir referans türü değildir, bu nedenle derleyici bu varsayımı yapamaz.

Ancak, bu daha açık olduğu için derlenecektir:

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

"Ancak, bir referans türü kullanmam durumunda == operatörü önceden tanımlanmış referans karşılaştırmasını kullanır mı yoksa bir tür tanımlıysa operatörün aşırı yüklenmiş sürümünü kullanır mı?"

Ben Generics üzerinde == aşırı yüklenmiş sürümünü kullanmak düşünürdüm, ama aşağıdaki test aksi gösterir. İlginç ... Nedenini bilmek isterdim! Birisi bilirse lütfen paylaşın.

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

Çıktı

Satır içi: Aşırı yüklenmiş == çağrılan

jenerik:

Devam etmek için herhangi bir tuşa basın . . .

Takip 2

Karşılaştırma yöntemimi değiştirmenin

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

aşırı yüklenmiş == operatörünün çağrılmasına neden olur. Tipini ( nerede ) belirtmeden tahmin ediyorum , derleyici aşırı yüklenmiş operatörü kullanması gerektiğini çıkaramaz ... gerçi tip belirtmeden bile karar vermek için yeterli bilgiye sahip olacağını düşünürdüm.


Teşekkürler. İfadenin sadece referans türleriyle ilgili olduğunu fark etmedim.
Hosam Aly

4
Ynt: Takip 2: Aslında derleyici bulduğu en iyi yöntemi bağlayacaktır, bu durumda Test.op_Equal. Ancak, Test'ten türeyen ve işleci geçersiz kılan bir sınıfınız varsa, Test'in işleci yine de çağrılır.
Hosam Aly

4
Belirtmek istediğim iyi bir uygulama, gerçek karşılaştırmayı her zaman geçersiz kılınan bir Equalsyöntemde ( ==operatörde değil ) yapmanız gerektiğidir .
jpbochi

11
Aşırı yük çözünürlüğü derleme zamanı olur. Dolayısıyla, ==genel tipler arasında olduğumuzda Tve Thangi kısıtlamaların taşındığı göz önüne alındığında, en iyi aşırı yük bulunursa, Tbunun için hiçbir zaman bir değer tipi (bu anlamsız bir sonuç verecek) kutulamayacağına dair özel bir kural vardır, bu nedenle referans türü olduğunu garanti eden bazı kısıtlamalar). Senin içinde Takip Up 2 gelirsin eğer, DerivedTestnesneler ve DerivedTesttüremiştir gelen Testancak sunmakta yeni bir aşırı yük ==, tekrar "sorun" olacaktır. Hangi aşırı yüklenme derleme zamanında IL'ye "yakılır".
Jeppe Stig Nielsen

1
wierdly bu genel referans türleri için çalışıyor gibi görünüyor (bu karşılaştırmanın referans eşitliğinde olmasını beklediğiniz yerde), ancak dizeler için de referans eşitliğini kullanıyor gibi görünüyor - böylece 2 özdeş dizeyi karşılaştırıp == ( sınıf yöntemi ile genel yöntem) farklı olduklarını söylüyorlar.
JonnyRaa

292

Diğerlerinin söylediği gibi, sadece T bir referans tipi olarak kısıtlandığında çalışır. Herhangi bir kısıtlama olmadan, null, ancak null ile karşılaştırabilirsiniz; bu karşılaştırma, null değeri olmayan değer türleri için her zaman yanlış olur.

Eşittir demek yerine IComparer<T>- kullanmak daha iyidir ve daha fazla bilginiz yoksa EqualityComparer<T>.Defaultiyi bir seçimdir:

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

Başka bir şey dışında, bu boks / dökümden kaçınır.


Teşekkürler. Basit bir sarmalayıcı sınıfı yazmaya çalışıyordum, bu yüzden sadece gerçek sarılmış üyeye operasyonu devretmek istedim. Ama EqualityComparer'ı bilmek <Ve> Varsayılan kesinlikle bana değer kattı. :)
Hosam Aly

Küçük bir kenara, Jon; Benim yazımda yorum pobox vs yoda yeniden not etmek isteyebilirsiniz.
Marc Gravell

4
EqualityComparer <T> kullanarak güzel ipucu
chakrit

1
Null ile karşılaştırılabileceğini ve null değeri olmayan değer türü için her zaman yanlış olacağını işaret eden +1
Jalal Said

@BlueRaja: Evet, çünkü boş gerçek ile karşılaştırmalar için özel kurallar var. Bu nedenle "herhangi bir kısıtlama olmadan, null, ancak null ile karşılaştırabilirsiniz". Zaten cevapta. Peki, bu neden tam olarak doğru olmayabilir?
Jon Skeet

41

Genel olarak, EqualityComparer<T>.Default.Equalsişi uygulayan IEquatable<T>veya mantıklı bir Equalsuygulaması olan herhangi bir şeyle yapmalıdır .

Bununla birlikte, ==ve Equalsherhangi bir nedenle farklı şekilde uygulanırsa, genel operatörler üzerindeki çalışmamın yararlı olması gerekir; (diğerleri arasında) operatör sürümlerini destekler :

  • Eşit (T değeri1, T değeri2)
  • Eşit Değil (T değeri1, T değeri2)
  • GreaterThan (T değeri1, T değeri2)
  • LessThan (T değeri1, T değeri2)
  • GreaterThanOrEqual (T değeri1, T değeri2)
  • LessThanOrEqual (T değeri1, T değeri2)

Çok ilginç bir kütüphane! :) (Yan not: www.yoda.arachsys.com bağlantısını kullanmanızı önerebilirim, çünkü pobox biri işyerimdeki güvenlik duvarı tarafından engellendi mi? Başkalarının da aynı sorunla karşılaşması mümkündür.)
Hosam Aly

Fikir, pobox.com/~skeet'in başka bir yere taşınsa bile her zaman web sitemi göstereceğidir . Ben posterity uğruna pobox.com üzerinden bağlantılar gönderme eğilimindedir - ancak şu anda bunun yerine yoda.arachsys.com adresini kullanabilirsiniz .
Jon Skeet

Pobox.com ile ilgili sorun, web tabanlı bir e-posta hizmeti (veya şirketin güvenlik duvarının söylediği) olmasıdır, bu yüzden engellenir. Bu yüzden bağlantısını takip edemedim.
Hosam Aly

"Ancak, == ve Eşittir nedense farklı uygulanır" - Kutsal sigaralar! Ancak ne! Belki de tam tersine bir kullanım durumu görmem gerekiyor, ancak ıraksama eşit olan bir kütüphane jeneriklerle ilgili sorundan daha büyük sorunlara yol açacaktır.
Edward Brey

@EdwardBrey yanlış değilsin; derleyici bunu zorlamak güzel olurdu, ama ...
Marc Gravell

31

Çok fazla cevap var, tek bir cevap neden değil? (Giovanni açıkça sordu) ...

.NET jenerikleri C ++ şablonları gibi davranmaz. C ++ şablonlarında, gerçek şablon parametreleri bilindikten sonra aşırı yük çözünürlüğü oluşur.

.NET jeneriklerinde (C # dahil), aşırı yük çözünürlüğü gerçek genel parametreleri bilmeden oluşur. Derleyicinin çağrılacak işlevi seçmek için kullanabileceği tek bilgi, genel parametrelerdeki tür kısıtlamalarından gelir.


2
ama neden derleyici onlara genel bir nesne gibi davranmıyor? sonuçta ==referans türleri veya değer türleri olmak üzere tüm türler için çalışır. Cevap verdiğini sanmadığım soru bu olmalı.
nawfal

4
@nawfal: Aslında hayır, ==tüm değer türleri için çalışmaz. Daha da önemlisi, tüm türler için aynı anlama sahip değildir, bu nedenle derleyici onunla ne yapacağını bilmez.
Ben Voigt

1
Ben, oh evet, yaratmadan yapabileceğimiz özel yapıları kaçırdım ==. Bu kısmı da cevabınıza
ekleyebilir misiniz

12

Derleme, T'nin bir yapı (değer türü) olamayacağını bilemez. Bu yüzden sadece referans türü olduğunu düşünüyorum söylemek gerekir:

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

Çünkü T bir değer türü olabiliyorsa, x == yhastalığın oluşturulacağı durumlar olabilir - bir türün operatörü == tanımlı değilse. Aynı şey daha açık olan için de olacak:

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

Bu da başarısız olur, çünkü foo işlevi olmayan bir T tipini geçebilirsiniz. C # sizi tüm olası türlerin her zaman foo işlevine sahip olduğundan emin olmaya zorlar. Bu nerede fıkra tarafından yapılır.


1
Açıklama için teşekkürler. Değer türlerinin kutunun dışında == operatörünü desteklemediğini bilmiyordum.
Hosam Aly

1
Hosam, gmcs (mono) ile test ettim ve her zaman referansları karşılaştırır. (yani T için isteğe bağlı olarak tanımlanmış bir operatör == kullanmaz)
Johannes Schaub - litb

Bu çözümde bir uyarı vardır: operatör == aşırı yüklenemez; bu StackOverflow sorusuna bakın .
Dimitri C.27

8

Sınıf kısıtı olmadan:

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

Bir fark gerekirken bu classkısıtlı Equalsiçinde ==gelen operatör INHERITS Object.Equalsbir yapı bindirilmesi o süre ValueType.Equals.

Bunu not et:

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

aynı derleyici hatasını verir.

Henüz bir değer tipi eşitlik operatörü karşılaştırmasının neden derleyici tarafından reddedildiğini anlamıyorum. Gerçi bunun bir işe yaradığını biliyorum:

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

u biliyorum toplam c # noob im. ama bence başarısız oluyor çünkü derleyici ne yapacağını bilmiyor. T henüz bilinmediği için, değer türlerine izin verilecekse T türüne bağlıdır. referanslar için referanslar T.'den bağımsız olarak karşılaştırılır .Equals, sonra .Equal sadece çağrılır.
Johannes Schaub - litb

ancak bir değer türünde == yaparsanız, değer türünün söz konusu işleci uygulamak zorunda kalması gerekmez.
Johannes Schaub - litb

Bu anlamlıdır, litb :) Kullanıcı tanımlı yapıların aşırı yüklenmemesi mümkündür ==, bu nedenle derleyici başarısız olur.
Jon Limjap

2
İlk karşılaştırma yöntemi yok değil kullanmak Object.Equalsyerine referans eşitliğini test eder. Örneğin Compare("0", 0.ToString()), argümanlar, her ikisi de tek karakterleri olarak sıfır olan farklı dizelere referanslar olacağından false döndürür.
supercat

1
Sonuncusunda küçük bir şey var - onu yapılarla sınırlandırmadınız, bu yüzden NullReferenceExceptionolabilir.
Flynn1179

6

Benim durumumda eşitlik operatörünün birim testini yapmak istedim. Ben açıkça genel türü ayarlamadan eşitlik operatörleri altında kodu çağırmak gerekiyordu. İçin önerilerde EqualityComparergibi yararlı değil EqualityComparerdenilen Equalsyöntemle ancak eşitlik operatörü.

İşte a LINQ. Operatörler ==ve !=operatörler için doğru kodu çağırır :

/// <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

Burada bunun için bir MSDN Connect girişi var

Alex Turner'ın yanıtı şununla başlar:

Ne yazık ki, bu davranış tasarım gereğidir ve == değer türlerini içerebilecek tür parametreleriyle kullanımını etkinleştirmek için kolay bir çözüm yoktur.


4

Özel türünüzün operatörlerinin çağrıldığından emin olmak istiyorsanız, bunu yansıma yoluyla yapabilirsiniz. Sadece genel parametrenizi kullanarak yazın ve istenen operatör için MethodInfo'yu alın (örn. Op_Equality, op_Inequality, op_LessThan ...).

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

Ardından, MethodInfo's Invoke yöntemini kullanarak operatörü yürütün ve nesneleri parametre olarak iletin.

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

Bu, aşırı yük operatörünüzü çağırır, genel parametreye uygulanan kısıtlamalar tarafından tanımlanan operatörü çağırmaz. Pratik olmayabilir, ancak birkaç test içeren genel bir temel sınıf kullanırken operatörlerinizi birim test etmek için kullanışlı olabilir.


3

En son msdn bakarak aşağıdaki işlevi yazdı. İki nesneyi kolayca karşılaştırabilir xve y:

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

4
Sen booleans kurtulmak ve yazmakreturn ((IComparable)(x)).CompareTo(y) <= 0;
aloisdg taşınma codidact.com için

1

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

Yukarıdakiler işe yarayacaktır çünkü kullanıcı tanımlı referans türlerinde == dikkate alınır.
Değer türleri durumunda, == geçersiz kılınabilir. Bu durumda, "! =" De tanımlanmalıdır.

Bunun nedeni olabileceğini düşünüyorum, "==" kullanarak genel karşılaştırmaya izin vermiyor.


2
Teşekkürler. Referans türlerinin operatörü de geçersiz kılabileceğine inanıyorum. Ancak başarısızlığın nedeni artık açık.
Hosam Aly

1
==Belirteci iki farklı operatörler için kullanılır. Verilen işlenen tipleri için eşitlik operatörünün uyumlu bir aşırı yükü varsa, bu aşırı yük kullanılır. Aksi takdirde, her iki işlenen de birbiriyle uyumlu referans türleriyse, bir referans karşılaştırması kullanılır. O Not Comparederleyici yukarıdaki yöntemle ilk anlamı geçerli olduğunu söyleyemem, ama çok, İkinci anlamı geçerlidir söyleyebilir ==belirteç ikincisi kullanacağız bile Taşırı yükler eşitlik-çek operatörü (örneğin 's türü ise String) .
supercat

0

.Equals()Ederken benim için çalışan TKeybir genel türüdür.

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


    // ...
}

Bu x.Id.Equalsdeğil id.Equals. Muhtemelen, derleyici türü hakkında bir şeyler biliyor x.
Hosam Aly
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.