it-swarm-tr.com

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

Bunun gibi tanımlanmış genel bir yöntemim var:

public void MyMethod<T>(T myArgument)

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

if (myArgument == default(T))

Ancak bu derleme yapmaz çünkü T'nin == işlecini uygulayacağını garanti etmedim. Bu yüzden kodu şuna değiştirdim:

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

Şimdi bu derleme yapar, ancak benim Argümanım boşsa, test ettiğim şeyin bir parçası. Bunun gibi açık bir null çek ekleyebilirim:

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

Şimdi bu bana gereksiz geliyor. ReSharper, myArgument == null bölümünü myArgument == default (T) olarak değiştirdiğimi, hangi noktada başladığımı gösteriyor. Bu sorunu çözmenin daha iyi bir yolu var mı?

Desteğe ihtiyacım var her ikisi de türleri ve değer türlerini referanslar.

239
Stefan Moser

Boks etmekten kaçınmak için, jenerikliği eşitlikle karşılaştırmanın en iyi yolu EqualityComparer<T>.Default iledir. Bu, IEquatable<T> (boks olmadan) ve object.Equals'a saygı gösterir ve tüm Nullable<T> "kaldırılmış" nüanslarını işler. Dolayısıyla:

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

Bu eşleşecek:

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

Buna ne dersin:

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

static object.Equals() yöntemini kullanmak, null kontrolünü yapmanıza gerek kalmaz. Çağrının açıkça object. ile nitelenmesi, bağlamınıza bağlı olarak muhtemelen gerekli değildir, ancak normalde sadece kodu daha çözülebilir hale getirmek için static çağrıları türünün adını yazın.

113
Kent Boogaart

Bu konuyu biraz ayrıntılı olarak tartışan bir Microsoft Connect makalesi bulabildim:

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

Türlerin referans türleri olduğu biliniyorsa, nesne üzerinde tanımlanan varsayılan aşırı yük, referans eşitliği için değişkenleri test eder, ancak bir tür kendi özel aşırı yükünü belirtebilir. Derleyici, değişkenin statik tipine göre kullanılacak aşırı yükü belirler (belirleme polimorfik değildir). Bu nedenle, örneğinizi T genel tür parametresini mühürlü olmayan bir referans türüyle (İstisna gibi) sınırlayacak şekilde değiştirirseniz, derleyici kullanılacak belirli aşırı yükü belirleyebilir ve aşağıdaki kod derlenebilir:

public class Test<T> where T : Exception

Türlerin değer türleri olduğu biliniyorsa, kullanılan kesin türlere dayalı olarak 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 özel değer karşılaştırmasını yayacağını bilmediğinden, burada iyi bir "varsayılan" karşılaştırma yoktur. Derleyici, ValueType.Equals (Object) çağrısı yapabilir ancak bu yöntem yansıma kullanır ve belirli değer karşılaştırmalarıyla karşılaştırıldığında oldukça yetersizdir. Bu nedenle, T üzerinde bir değer türü kısıtı belirlemeseniz bile, derleyicinin burada üretmesi için makul bir şey yoktur:

public class Test<T> where T : struct

Sunucunuzda, derleyicinin T'nin bir değer ya da referans türü olup olmadığını bile bilmediği durumlarda, benzer şekilde, olası tüm türler için geçerli olacak hiçbir şey yoktur. Değer karşılaştırması için referans karşılaştırması geçerli olmayacak ve aşırı yüklenmeyen referans tipleri için bir tür değer karşılaştırması beklenmeyecektir.

İşte yapabilecekleriniz ...

Her iki yöntemin de referans ve değer türlerinin genel 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şleciyle karşılaştırma yapmak için aşağıdaki yöntemlerden birini kullanmanız gerekir:

Tüm T durumları bilinen bir temel sınıftan çıkarsa, derleyiciye genel tür kısıtlamaları kullanarak bildirebilirsiniz.

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

Derleyici daha sonra MyBase üzerinde nasıl işlem yapılacağını tanır ve şu anda gördüğünüz 'T' ve 'T' tipindeki işlenenlere "Operator '==' komutunu atamaz.

Başka bir seçenek de T'yi IComparable öğesini uygulayan herhangi bir türle sınırlamak olacaktır.

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

Ve sonra IComparable interface ile tanımlanan CompareTo yöntemini kullanın.

24
Eric Schoonover

Bunu dene:

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

derlemeli ve ne istersen onu yap.

(Edited)

Marc Gravell en iyi cevaba sahip, ancak göstermek için uğraştığım basit bir kod pasajı göndermek istedim. Sadece bunu basit bir C # konsol uygulamasında çalıştırın:

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: VS2008 olan bir kişi bunu bir uzatma yöntemi olarak deneyebilir mi? 2005 yılında burada kaldım ve buna izin verip vermeyeceğimi merak ediyorum.


Edit: İşte uzatma 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));
    }
}
7
Joel Coehoorn

T'nin ilkel tip olduğu yerler dahil olmak üzere tüm T türlerini ele almak 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;
    }
6
Nick Farina

Burada bir sorun olacak -

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

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

Alternatif olarak, bunun üzerine bir arayüz kısıtlaması koyabilirsiniz ve arayüz sınıf/yapı sınıfının varsayılanını kontrol etmek için bir yol sağlayabilir.

2
Reed Copsey

Bence büyük olasılıkla bu mantığı iki bölüme ayırmanız ve önce null değerini kontrol etmeniz gerekir.

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üretilen bir sınıfsa, bunun zaten boş olmadığını biliyoruz. Öte yandan, eğer bir değer türü değilse, o zaman değer nesnesini null değerine karşı karşılaştırabiliriz. Doğruca bir oyuncu kadrosuna giderek ValueType kontrolünü önleyebiliriz, ancak bu, yığın üzerinde yeni bir nesne yaratıldığını ima ettiğinden, muhtemelen kaçınmak istediğimiz bir değer türünün kutucuğu anlamına gelir.

IsNullOrEmpty yönteminde bir dizgenin özel durumunu kontrol ediyoruz. Diğer tüm türler için (zaten/ değil null bilen) değerini, tüm referans türleri için null olan ve değer türleri için genellikle sıfır biçimindeki varsayılan değerle karşılaştırırız (eğer integral ise). ).

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
Damian Powell

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;
  }
}
0
kofifus