C # içindeki genel argümanın boş veya varsayılan karşılaştırması


288

Ben böyle tanımlanmış genel bir yöntem var:

public void MyMethod<T>(T myArgument)

Yapmak istediğim ilk şey, myArgument değerinin bu tür için varsayılan değer olup olmadığını kontrol etmektir:

if (myArgument == default(T))

Ancak bu derlenmez çünkü T'nin == operatörünü uygulayacağını garanti etmedim. Bu yüzden kodu şu şekilde değiştirdim:

if (myArgument.Equals(default(T)))

Şimdi bu derlenir, ancak myArgument null olduğunda başarısız olur, bu da test ettiğim şeyin bir parçasıdır. Ben böyle bir açık null çek ekleyebilirsiniz:

if (myArgument == null || myArgument.Equals(default(T)))

Şimdi bu bana gereksiz geliyor. ReSharper, myArgument == null parçasını başladığım yer olan myArgument == default (T) olarak değiştirmemi bile önerir. Bu sorunu çözmenin daha iyi bir yolu var mı?

Hem referans türlerini hem de değer türlerini desteklemem gerekiyor .


C # artık verdiğiniz son örnek için sözdizimsel şeker olan Null Koşullu Operatörleri desteklemektedir . Kodunuz olur if (myArgument?.Equals( default(T) ) != null ).
wizard07KSU

1
@ wizard07KSU Bu değer türleri için çalışmaz, yani trueher durumda değerlendirilir, çünkü Equalswil her zaman değer türleri için çağrılır çünkü bu durumda myArgumentolamaz nullve Equals(bir boolean) sonucu asla olmayacaktır null.
jasper

Eşit derecede değerli neredeyse yinelenen (kapatmak için oy vermiyor): Operatör ==, C #'daki genel türlere uygulanamıyor mu?
GSerg

Yanıtlar:


585

Bokstan kaçınmak için, jenerikleri eşitlikle karşılaştırmanın en iyi yolu şudur EqualityComparer<T>.Default. Bu, IEquatable<T>(bokssuz) yanı sıra object.Equalstüm Nullable<T>"kaldırılmış" nüanslara da saygı gösterir . Dolayısıyla:

if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
    return obj;
}

Bu eşleşecek:

  • sınıflar için null
  • için boş (boş) Nullable<T>
  • diğer yapılar için sıfır / yanlış / vb.

29
Vay canına, ne kadar nefis! Bu kesinlikle gitmek için bir yoldur, kudos.
Nick Farina

1
Kesinlikle en iyi cevap. Bu çözümü kullanmak için yeniden yazdıktan sonra kodumda dalgalı çizgiler yok.
Nathan Ridley

14
Mükemmel cevap! Daha da iyisi, obj.IsDefaultForType ()
rikoe 6:10

2
durumunda @nawfal Person, p1.Equals(p2)bunun olmadığını uygular bağlı olacağı IEquatable<Person>kamu API üzerinde veya açık uygulanması yoluyla - yani derleyici bir kamu görebilirsiniz Equals(Person other)yöntem. Ancak; içerisinde jenerik , aynı IL tümü için kullanılan T; Bir T1uygulamaya olur IEquatable<T1>ihtiyaçlarını a aynı şekilde muamele edilecek T2böylece hayır, - etmediğini olmayacak bir nokta Equals(T1 other)da zamanında var olsa bile, yöntem. Her iki durumda da null(her iki nesne) de düşünmek gerekir. Yani jenerikler ile, gönderdiğim kodu kullanırdım.
Marc Gravell

5
Bu cevabın beni delilikten uzaklaştırıp uzaklaştırmayacağına karar veremiyorum. +1
Steven Liekens

118

Buna ne dersin:

if (object.Equals(myArgument, default(T)))
{
    //...
}

static object.Equals()Yöntemi kullanmak, nullkontrolü kendiniz yapmanız gereğini ortadan kaldırır . object.Bağlamınıza bağlı olarak çağrıyı muhtemelen nitelendirmek gerekli değildir, ancak normalde staticsadece kodu daha çözünür hale getirmek için çağrıları tür adıyla öneklidir.


2
"Nesneyi" bile bırakabilirsiniz. çünkü gereksizdir. if (Eşittir (myArgument, varsayılan (T)))
Stefan Moser

13
Doğru, normalde budur, ancak bağlama bağlı olmayabilir. İki bağımsız değişken alan bir örnek Equals () yöntemi olabilir. Ben sadece kodu daha kolay okumak için, tüm statik çağrıları sınıf adı ile açıkça önek eğilimindedir.
Kent Boogaart

8
Boksa

2
Benim için zaten kutulu olan tam sayıları kullanırken bu işe yaramaz. O zaman bir nesne olacağından ve nesne için varsayılan 0 yerine null olduğundan.
riezebosch

28

Bu sorunu ayrıntılı olarak açıklayan bir Microsoft Connect makalesi bulabildim :

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

Türlerin referans türleri olduğu biliniyorsa, nesne kendi özel aşırı yüklenmesini belirtebilse de, nesne üzerinde tanımlanan varsayılan aşırı yük, referans eşitliğini test eder. Derleyici, değişkenin statik tipine göre hangi aşırı yüklenmenin kullanılacağını belirler (belirleme polimorfik değildir). Bu nedenle, genel tip parametresi T'yi mühürlü olmayan bir başvuru tipiyle (İstisna gibi) sınırlamak için örneğinizi değiştirirseniz, derleyici kullanılacak belirli aşırı yükü belirleyebilir ve aşağıdaki kod derlenir:

public class Test<T> where T : Exception

Türlerin değer türleri olduğu biliniyorsa, kullanılan kesin türlere göre belirli değer eşitliği testleri gerçekleştirir. Referans karşılaştırmaları değer türleri üzerinde anlamlı olmadığından ve derleyici hangi belirli değer karşılaştırmasının yayılacağını bilemediğinden burada iyi bir "varsayılan" karşılaştırma yoktur. Derleyici ValueType.Equals (Object) çağrısı yayabilir, ancak bu yöntem yansıma kullanır ve belirli değer karşılaştırmalarına kıyasla oldukça verimsizdir. Bu nedenle, T'de bir değer türü kısıtlaması belirtmiş olsanız bile, derleyicinin burada oluşturması için makul bir şey yoktur:

public class Test<T> where T : struct

Derleyicinin T'nin bir değer mi, yoksa referans tipi mi olduğunu bile bilmediği durumlarda, benzer şekilde üretilecek tüm olası tipler için geçerli olacak hiçbir şey yoktur. Referans karşılaştırması değer türleri için geçerli olmaz ve aşırı yüklenmeyen referans türleri için bir tür değer karşılaştırması beklenmez.

İşte yapabilecekleriniz ...

Bu yöntemlerin her ikisinin de referans ve değer türlerinin genel bir karşılaştırması için çalıştığını doğruladım:

object.Equals(param, default(T))

veya

EqualityComparer<T>.Default.Equals(param, default(T))

"==" işleci ile karşılaştırma yapmak için şu yöntemlerden birini kullanmanız gerekir:

Tüm T vakaları bilinen bir temel sınıftan türüyorsa, derleyiciye genel tür kısıtlamaları kullanarak bilgi verebilirsiniz.

public void MyMethod<T>(T myArgument) where T : MyBase

Derleyici daha sonra işlemlerin nasıl yapılacağını tanır MyBaseve şu anda gördüğünüz "T" ve "T" türündeki işlemcilere "Operator '==" uygulanamaz.

Başka bir seçenek de T'yi uygulayan herhangi bir türle kısıtlamak olacaktır IComparable.

public void MyMethod<T>(T myArgument) where T : IComparable

Ve sonra IComparable arabirimiCompareTo tarafından tanımlanan yöntemi kullanın .


4
"bu davranış tasarım gereğidir ve değer türlerini içerebilecek tür parametreleriyle kullanımının kolay bir çözümü yoktur." Aslında Microsoft yanlış. Kolay bir çözüm vardır: MS, ceq op kodunu, değer türlerinde bitsel operatör olarak işlev görecek şekilde genişletmelidir. Sonra sadece bu opcode'u kullanan bir içsel sağlayabilirler, örn. Object.BitwiseOrReferenceEquals <T> (değer, varsayılan (T)) sadece ceq kullanan. Hem değer hem de referans türleri için bu , değerin bitsel eşitliğini kontrol eder (ancak referans türleri için referans bitsel eşitliği object.ReferenceEquals ile aynıdır)
Qwertie

1
İstediğiniz Microsoft Connect bağlantısının connect.microsoft.com/VisualStudio/feedback/details/304501/…
Qwertie

18

Bunu dene:

if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))

derlemeli ve istediğini yapmalısın.


<code> varsayılan (T) </code> gereksiz değil mi? <code> EqualityComparer <T> .Default.Equals (myArgument) </code> hile yapmalıdır.
Joshcodes

2
1) denediniz mi ve 2) karşılaştırma nesnesiyle neyi karşılaştırıyorsunuz? EqualsYöntem IEqualityCompareriki argüman alır, iki nesne böylece hayır, gereksiz değildir, karşılaştırma.
Lasse V.Karlsen

Bu, IMHO'nun kabul edilen cevabından bile daha iyidir, çünkü boks / kutulama ve diğer türleri işler. Bu "dupe olarak kapalı" sorularının cevabına bakınız: stackoverflow.com/a/864860/210780
ashes999

7

(Edited)

Marc Gravell en iyi cevaba sahip, ancak göstermek için çalıştığım basit bir kod pasajı göndermek istedim. Bunu basit bir C # konsol uygulamasında çalıştırmanız yeterlidir:

public static class TypeHelper<T>
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer<T>.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper<int>.IsDefault(1)); //False
    Console.WriteLine(TypeHelper<int>.IsDefault(0)); // True

    // reference type
    Console.WriteLine(TypeHelper<string>.IsDefault("test")); //False
    Console.WriteLine(TypeHelper<string>.IsDefault(null)); //True //True

    Console.ReadKey();
}

Bir şey daha var: VS2008'li biri bunu bir uzantı yöntemi olarak deneyebilir mi? Burada 2005 ile sıkışıp kaldım ve buna izin verilip verilmeyeceğini merak ediyorum.


Düzenleme: Nasıl bir uzantı yöntemi olarak çalışmasını sağlamak için:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // value type
        Console.WriteLine(1.IsDefault());
        Console.WriteLine(0.IsDefault());

        // reference type
        Console.WriteLine("test".IsDefault());
        // null must be cast to a type
        Console.WriteLine(((String)null).IsDefault());
    }
}

// The type cannot be generic
public static class TypeHelper
{
    // I made the method generic instead
    public static bool IsDefault<T>(this T val)
    {
        return EqualityComparer<T>.Default.Equals(val, default(T));
    }
}

3
Bir uzatma yöntemi olarak "çalışır". İlginçtir, çünkü o boş olduğunda o.IsDefault <object> () demenize rağmen çalışır. Korkunç =)
Nick Farina

6

T'nin ilkel bir tip olduğu yerler de dahil olmak üzere tüm T türlerini işlemek için her iki karşılaştırma yönteminde de derlemeniz gerekir:

    T Get<T>(Func<T> createObject)
    {
        T obj = createObject();
        if (obj == null || obj.Equals(default(T)))
            return obj;

        // .. do a bunch of stuff
        return obj;
    }

1
İşlev Func <T> ve T geri döndürmek için değiştirildi unutmayın, ben yanlışlıkla sorucunun kodundan atlandı düşünüyorum.
Nick Farina

ReSharper benimle uğraşıyor gibi görünüyor. Bir değer türü ile null arasındaki olası karşılaştırma hakkındaki uyarısının bir derleyici uyarısı olmadığını fark etmedi.
Nathan Ridley

2
Bilginize: T bir değer türü olduğu ortaya çıkarsa, null ile karşılaştırma titreşim tarafından her zaman yanlış kabul edilir.
Eric Lippert

Mantıklı - çalışma zamanı bir işaretçiyi bir değer türüyle karşılaştırıyor olacak. Equals () denetimi bu durumda işe yarar (ilginçtir, çünkü derlemek için 5.Equals (4) demek çok dinamik bir dil gibi görünüyor).
Nick Farina

2
Boks etmeyen bir alternatif için EqualityComparer <T> cevabına bakınız.
Marc Gravell

2

Burada bir sorun olacak -

Bunun herhangi bir tür için çalışmasına izin verirseniz, referans türleri için varsayılan (T) her zaman ve değer türleri için 0 (veya 0 ile dolu yapı) boş olur.

Bu muhtemelen peşinde olduğunuz davranış değildir. Bunun genel bir şekilde çalışmasını istiyorsanız, muhtemelen T türünü kontrol etmek ve referans türlerinden farklı değer türlerini işlemek için yansıma kullanmanız gerekir.

Alternatif olarak, buna bir arabirim kısıtlaması koyabilirsiniz ve arabirim, sınıfın / yapının varsayılan değerlerini denetlemenin bir yolunu sağlayabilir.


1

Sanırım muhtemelen bu mantığı iki parçaya bölmeniz ve null olup olmadığını kontrol etmeniz gerekiyor.

public static bool IsNullOrEmpty<T>(T value)
{
    if (IsNull(value))
    {
        return true;
    }
    if (value is string)
    {
        return string.IsNullOrEmpty(value as string);
    }
    return value.Equals(default(T));
}

public static bool IsNull<T>(T value)
{
    if (value is ValueType)
    {
        return false;
    }
    return null == (object)value;
}

IsNull yönteminde, ValueType nesnelerinin tanım gereği boş olamayacağına güveniyoruz, bu nedenle değer ValueType'tan türeyen bir sınıfsa, zaten boş olmadığını biliyoruz. Öte yandan, bir değer türü değilse, bir nesneye verilen değeri null ile karşılaştırabiliriz. Doğrudan nesneye bir döküm uygulamasına giderek ValueType'a karşı denetimden kaçınabiliriz, ancak bu, bir değer türünün kutuyu alacağı anlamına gelir; bu, yığın üzerinde yeni bir nesne oluşturulduğunu ima ettiğinden muhtemelen kaçınmak istediğimiz bir şeydir.

IsNullOrEmpty yönteminde, bir dizenin özel durumunu kontrol ediyoruz. Diğer tüm türler için, değeri (zaten boş olduğunu bilmeyen ), tüm referans türleri için null olan ve değer türleri için genellikle bir çeşit sıfır olan (tümleşikse) varsayılan değerle karşılaştırıyoruz.

Bu yöntemleri kullanarak, aşağıdaki kod beklediğiniz gibi davranır:

class Program
{
    public class MyClass
    {
        public string MyString { get; set; }
    }

    static void Main()
    {
        int  i1 = 1;    Test("i1", i1); // False
        int  i2 = 0;    Test("i2", i2); // True
        int? i3 = 2;    Test("i3", i3); // False
        int? i4 = null; Test("i4", i4); // True

        Console.WriteLine();

        string s1 = "hello";      Test("s1", s1); // False
        string s2 = null;         Test("s2", s2); // True
        string s3 = string.Empty; Test("s3", s3); // True
        string s4 = "";           Test("s4", s4); // True

        Console.WriteLine();

        MyClass mc1 = new MyClass(); Test("mc1", mc1); // False
        MyClass mc2 = null;          Test("mc2", mc2); // True
    }

    public static void Test<T>(string fieldName, T field)
    {
        Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field));
    }

    // public static bool IsNullOrEmpty<T>(T value) ...

    // public static bool IsNull<T>(T value) ...
}

1

Kabul edilen cevaba dayalı uzatma yöntemi.

   public static bool IsDefault<T>(this T inObj)
   {
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

Kullanımı:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue == null || tValue.IsDefault()) return false;
   }

Basitleştirmek için null ile alternatif verin:

   public static bool IsNullOrDefault<T>(this T inObj)
   {
       if (inObj == null) return true;
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

Kullanımı:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue.IsNullOrDefault()) return false;
   }

0

Kullanırım:

public class MyClass<T>
{
  private bool IsNull() 
  {
    var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
    return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
  }
}

-1

Bunun gereksinimlerinizle çalışıp çalışmadığını bilmiyorum, ancak T'yi IComparable gibi bir arabirim uygulayan bir Tür olarak kısıtlayabilir ve daha sonra bu arabirimdeki ComparesTo () yöntemini (IIRC'nin desteklediği / boşta olduğu gibi) kullanabilirsiniz. :

public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))

IEquitable gibi kullanabileceğiniz başka arayüzler de vardır.


OP NullReferenceException konusunda endişeli ve siz de ona aynı şeyi garanti ediyorsunuz.
nawfal

-2

@ilitirit:

public class Class<T> where T : IComparable
{
    public T Value { get; set; }
    public void MyMethod(T val)
    {
        if (Value == val)
            return;
    }
}

'==' operatörü, 'T' ve 'T' tipi işlenenlere uygulanamaz

Yukarıda açıklandığı gibi Equals yöntemi veya object.Equals çağırdıktan sonra açık null sınama olmadan bunu yapmanın bir yol düşünemiyorum.

System.Comparison kullanarak bir çözüm tasarlayabilirsiniz, ancak bu gerçekten daha fazla kod satırı ile sonuçlanacak ve karmaşıklığı önemli ölçüde artıracaktır.


-3

Sanırım yakındın.

if (myArgument.Equals(default(T)))

Şimdi bu derlenir, ancak myArgumentnull ise başarısız olur , bu da test ettiğim şeyin bir parçası. Ben böyle bir açık null çek ekleyebilirsiniz:

Zarif bir sıfır-güvenli yaklaşım için eşitlerin çağrıldığı nesneyi ters çevirmeniz yeterlidir.

default(T).Equals(myArgument);

Ben de aynı şeyi düşünüyordum.
Chris Gessler

6
başvuru türünün varsayılan değeri (T) null olur ve garantili bir NullReferenceException özelliğine neden olur.
Stefan Steinegger
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.