Null yapılabilir her şey için C # genel tür kısıtlaması


111

Bu yüzden bu sınıfa sahibim:

public class Foo<T> where T : ???
{
    private T item;

    public bool IsNull()
    {
        return item == null;
    }

}

Şimdi, olabilecek her şeyi tür parametresi olarak kullanmama izin veren bir tür kısıtlaması arıyorum null. Bu, tüm Nullable( T?) türlerinin yanı sıra tüm referans türleri anlamına gelir :

Foo<String> ... = ...
Foo<int?> ... = ...

mümkün olmalıdır.

Kullanılması classtür kısıtlaması sadece bana referans türlerini kullanmak için imkanı verir.

Ek Bilgi: Bir boru ve filtre uygulaması yazıyorum nullve boru hattına geçen son öğe olarak bir referans kullanmak istiyorum , böylece her filtre güzelce kapanabilir, temizlik yapabilir, vb ...


1
@ Nullables'a izin vermeyen zaman
Rik

Bu bağlantı size yardımcı olabilir: social.msdn.microsoft.com/Forums/en-US/…
Réda Mattar

2
Bunu doğrudan yapmak mümkün değil. Belki bize senaryonuz hakkında daha fazla bilgi verebilirsiniz? Ya da belki IFoo<T>çalışma türü olarak kullanabilir ve bir fabrika yöntemiyle örnekler oluşturabilirsiniz? Bu işe yarayabilir.
Jon

Bir şeyi neden bu şekilde kısıtlamak isteyip istemediğinizden emin değilim. Tek amacınız "if x == null" ifadesini if ​​x.IsNull () "haline getirmekse, bu, önceki sözdizimine alışkın olan geliştiricilerin% 99,99'u için anlamsız ve sezgisel görünmüyor. Derleyici bunu yapmanıza izin vermeyecek" eğer (int) x == null "her neyse, zaten kapsam
dahilindesiniz

1
Bu, SO'da oldukça yaygın olarak tartışılmaktadır. stackoverflow.com/questions/209160/… ve stackoverflow.com/questions/13794554/…
Maxim Gershkovich

Yanıtlar:


22

Derleme zamanı denetimi yerine Foo'nun yapıcısında çalışma zamanı denetimi yapmaya istekliysen, türün başvuru veya null atanabilir tür olup olmadığını kontrol edebilir ve durum buysa bir istisna atabilirsin.

Yalnızca bir çalışma zamanı kontrolüne sahip olmanın kabul edilemez olabileceğinin farkındayım, ancak her ihtimale karşı:

public class Foo<T>
{
    private T item;

    public Foo()
    {
        var type = typeof(T);

        if (Nullable.GetUnderlyingType(type) != null)
            return;

        if (type.IsClass)
            return;

        throw new InvalidOperationException("Type is not nullable or reference type.");
    }

    public bool IsNull()
    {
        return item == null;
    }
}

Ardından aşağıdaki kod derlenir, ancak sonuncusu ( foo3) yapıcıda bir istisna atar:

var foo1 = new Foo<int?>();
Console.WriteLine(foo1.IsNull());

var foo2 = new Foo<string>();
Console.WriteLine(foo2.IsNull());

var foo3= new Foo<int>();  // THROWS
Console.WriteLine(foo3.IsNull());

31
Bunu yapacaksanız, statik
kurucuda

2
@EamonNerbonne Statik oluşturuculardan istisnalar yükseltmemelisiniz: msdn.microsoft.com/en-us/library/bb386039.aspx
Matthew Watson

5
Yönergeler mutlak değildir. Bu kontrolü istiyorsanız, çalışma zamanı kontrolünün maliyeti ile statik kurucudaki istisnaların işleyişsizliği arasında denge kurmanız gerekecektir. Burada gerçekten zayıf bir statik analizci uyguladığınız için, bu istisna, geliştirme sırasında dışında asla atılmamalıdır. Son olarak, her ne pahasına olursa olsun (akıllıca olmayan) statik yapı istisnalarından kaçınmak isteseniz bile, yine de mümkün olduğunca statik olarak ve örnek kurucuda mümkün olduğunca az çalışmalısınız - örneğin bir bayrak "isBorked" veya her neyse.
Eamon Nerbonne

Bu arada, bunu hiç yapmaya çalışman gerektiğini düşünmüyorum. Çoğu durumda, sızdıran, başarısızlığa meyilli bir soyutlama ile çalışmak yerine, bunu sadece bir C # sınırlaması olarak kabul etmeyi tercih ederim. Örneğin, farklı bir çözüm yalnızca sınıflar gerektirebilir veya yalnızca yapı gerektirebilir (ve açıkça em nullable yapmak) - veya her ikisini de yapıp iki sürümü olabilir. Bu, bu çözüme yönelik bir eleştiri değil; sadece bu problem iyi bir şekilde çözülemez - tabii, özel bir roslyn analizörü yazmak istemediğiniz sürece.
Eamon Nerbonne

1
Her iki dünyanın da en iyisini elde edebilirsiniz - static bool isValidTypestatik kurucuda ayarladığınız bir alanı tutun , ardından örnek oluşturucudaki bayrağı kontrol edin ve geçersiz bir türse atın, böylece her inşa ettiğinizde tüm kontrol işlerini yapmazsınız. bir örnek. Bu kalıbı sıklıkla kullanıyorum.
Mike Marynowski

20

Genelde OR ile eşdeğerini nasıl uygulayacağımı bilmiyorum . Ancak, null atanabilir türler için null ve yapılar için 0 değeri oluşturmak için varsayılan anahtar sözcüğü kullanmayı önerebilirim :

public class Foo<T>
{
    private T item;

    public bool IsNullOrDefault()
    {
        return Equals(item, default(T));
    }
}

Ayrıca Nullable sürümünüzü de uygulayabilirsiniz:

class MyNullable<T> where T : struct
{
    public T Value { get; set; }

    public static implicit operator T(MyNullable<T> value)
    {
        return value != null ? value.Value : default(T);
    }

    public static implicit operator MyNullable<T>(T value)
    {
        return new MyNullable<T> { Value = value };
    }
}

class Foo<T> where T : class
{
    public T Item { get; set; }

    public bool IsNull()
    {
        return Item == null;
    }
}

Misal:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(new Foo<MyNullable<int>>().IsNull()); // true
        Console.WriteLine(new Foo<MyNullable<int>> {Item = 3}.IsNull()); // false
        Console.WriteLine(new Foo<object>().IsNull()); // true
        Console.WriteLine(new Foo<object> {Item = new object()}.IsNull()); // false

        var foo5 = new Foo<MyNullable<int>>();
        int integer = foo5.Item;
        Console.WriteLine(integer); // 0

        var foo6 = new Foo<MyNullable<double>>();
        double real = foo6.Item;
        Console.WriteLine(real); // 0

        var foo7 = new Foo<MyNullable<double>>();
        foo7.Item = null;
        Console.WriteLine(foo7.Item); // 0
        Console.WriteLine(foo7.IsNull()); // true
        foo7.Item = 3.5;
        Console.WriteLine(foo7.Item); // 3.5
        Console.WriteLine(foo7.IsNull()); // false

        // var foo5 = new Foo<int>(); // Not compile
    }
}

Çerçevedeki orijinal Nullable <T> bir sınıf değil, bir yapıdır. Bir değer türünü taklit edecek bir referans türü sarmalayıcı oluşturmanın iyi bir fikir olduğunu düşünmüyorum.
Niall Connaughton

1
Varsayılanı kullanan ilk öneri mükemmel! Şimdi, döndürülen genel bir türe sahip şablonum, nesneler için bir boş değer ve yerleşik türler için varsayılan değer döndürebilir.
Casey Anderson

13

"Nullable" (referans türleri veya Nullable'lar) alabilen genel bir statik yöntem isteyen daha basit bir durum için bu sorunla karşılaştım, bu da beni tatmin edici bir çözüm olmadan bu soruya getirdi. Bu yüzden, basitçe iki aşırı yüklenmiş yönteme sahip olarak çözülmesi OP'nin belirttiği sorudan nispeten daha kolay olan kendi çözümümü buldum, biri a alan Tve kısıtlamaya sahip, where T : classdiğeri a T?ve sahip olan where T : struct.

Daha sonra, kurucuyu özel (veya korumalı) yaparak ve statik bir fabrika yöntemi kullanarak derleme zamanında kontrol edilebilen bir çözüm oluşturmak için çözümün bu soruna da uygulanabileceğini fark ettim:

    //this class is to avoid having to supply generic type arguments 
    //to the static factory call (see CA1000)
    public static class Foo
    {
        public static Foo<TFoo> Create<TFoo>(TFoo value)
            where TFoo : class
        {
            return Foo<TFoo>.Create(value);
        }

        public static Foo<TFoo?> Create<TFoo>(TFoo? value)
            where TFoo : struct
        {
            return Foo<TFoo?>.Create(value);
        }
    }

    public class Foo<T>
    {
        private T item;

        private Foo(T value)
        {
            item = value;
        }

        public bool IsNull()
        {
            return item == null;
        }

        internal static Foo<TFoo> Create<TFoo>(TFoo value)
            where TFoo : class
        {
            return new Foo<TFoo>(value);
        }

        internal static Foo<TFoo?> Create<TFoo>(TFoo? value)
            where TFoo : struct
        {
            return new Foo<TFoo?>(value);
        }
    }

Şimdi bunu şu şekilde kullanabiliriz:

        var foo1 = new Foo<int>(1); //does not compile
        var foo2 = Foo.Create(2); //does not compile
        var foo3 = Foo.Create(""); //compiles
        var foo4 = Foo.Create(new object()); //compiles
        var foo5 = Foo.Create((int?)5); //compiles

Parametresiz bir kurucu istiyorsanız, aşırı yükleme nezaketini elde edemezsiniz, ancak yine de şöyle bir şey yapabilirsiniz:

    public static class Foo
    {
        public static Foo<TFoo> Create<TFoo>()
            where TFoo : class
        {
            return Foo<TFoo>.Create<TFoo>();
        }

        public static Foo<TFoo?> CreateNullable<TFoo>()
            where TFoo : struct
        {
            return Foo<TFoo?>.CreateNullable<TFoo>();
        }
    }

    public class Foo<T>
    {
        private T item;

        private Foo()
        {
        }

        public bool IsNull()
        {
            return item == null;
        }

        internal static Foo<TFoo> Create<TFoo>()
            where TFoo : class
        {
            return new Foo<TFoo>();
        }

        internal static Foo<TFoo?> CreateNullable<TFoo>()
            where TFoo : struct
        {
            return new Foo<TFoo?>();
        }
    }

Ve bunu şu şekilde kullanın:

        var foo1 = new Foo<int>(); //does not compile
        var foo2 = Foo.Create<int>(); //does not compile
        var foo3 = Foo.Create<string>(); //compiles
        var foo4 = Foo.Create<object>(); //compiles
        var foo5 = Foo.CreateNullable<int>(); //compiles

Bu çözümün birkaç dezavantajı vardır, bunlardan biri, nesneleri oluşturmak için 'yeni'yi kullanmayı tercih edebilirsiniz. Başka Kullanmak mümkün olmayacak olmasıdır Foo<T>gibi bir şey bir tür kısıtlaması için genel tür bağımsız değişken olarak: where TFoo: new(). Son olarak, burada ihtiyacınız olan ve özellikle birden fazla aşırı yüklenmiş kurucuya ihtiyacınız varsa artacak olan fazladan kod bitidir.


8

Belirtildiği gibi, bunun için derleme zamanı kontrolüne sahip olamazsınız. .NET'teki genel kısıtlamalar ciddi ölçüde eksiktir ve çoğu senaryoyu desteklemez.

Ancak bunun çalışma zamanı denetimi için daha iyi bir çözüm olduğunu düşünüyorum. Her ikisi de sabit oldukları için JIT derleme zamanında optimize edilebilir.

public class SomeClass<T>
{
    public SomeClass()
    {
        // JIT-compile time check, so it doesn't even have to evaluate.
        if (default(T) != null)
            throw new InvalidOperationException("SomeClass<T> requires T to be a nullable type.");

        T variable;
        // This still won't compile
        // variable = null;
        // but because you know it's a nullable type, this works just fine
        variable = default(T);
    }
}

3

Böyle bir tür kısıtlaması mümkün değildir. Tür kısıtlamalarının belgelerine göre, hem null yapılabilir hem de başvuru türlerini yakalayan kısıtlama yoktur. Kısıtlamalar yalnızca bir bağlantılı olarak birleştirilebildiğinden, böyle bir kısıtlamayı kombinasyonla yaratmanın bir yolu yoktur.

Bununla birlikte, ihtiyaçlarınız için bir kısıtlama türü parametresine geri dönebilirsiniz, çünkü her zaman == null'u kontrol edebilirsiniz. Tür bir değer türü ise, kontrol her zaman yanlış olarak değerlendirilir. Ardından, anlambilim sizin için doğru olduğu sürece kritik olmayan "Değer türünün null ile olası karşılaştırması" R # uyarısını alırsınız.

Bir alternatif kullanmak olabilir

object.Equals(value, default(T))

null kontrol yerine, varsayılan (T) olduğundan, burada T: class her zaman boştur. Ancak bu, null yapılamayan bir değerin hiçbir zaman açıkça ayarlanmadığı veya yalnızca varsayılan değerine ayarlandığı hava durumunu ayırt edemeyeceğiniz anlamına gelir.


Bence sorun, bu değerin asla belirlenemediğini nasıl kontrol edeceğidir. Boş değerden farklı, değerin başlatıldığını gösteriyor gibi görünüyor.
Ryszard Dżegan

Değer türleri her zaman ayarlandığından (en azından örtük olarak ilgili varsayılan değerine) bu yaklaşımı geçersiz kılmaz.
Sven Amann

3

kullanırım

public class Foo<T> where T: struct
{
    private T? item;
}

-2
    public class Foo<T>
    {
        private T item;

        public Foo(T item)
        {
            this.item = item;
        }

        public bool IsNull()
        {
            return object.Equals(item, null);
        }
    }

    var fooStruct = new Foo<int?>(3);
        var b = fooStruct.IsNull();

        var fooStruct1 = new Foo<int>(3);
        b = fooStruct1.IsNull();

        var fooStruct2 = new Foo<int?>(null);
        b = fooStruct2.IsNull();

        var fooStruct3 = new Foo<string>("qqq");
        b = fooStruct3.IsNull();

        var fooStruct4 = new Foo<string>(null);
        b = fooStruct4.IsNull();

Bu tipleme, yeni Foo <int> (42) ve IsNull () 'un anlamsal olarak doğru olmasına rağmen özellikle anlamlı olmayan false döndürmesine izin verir.
RJ Lohan

1
42 "Yaşamın, Evrenin ve Her Şeyin Nihai Sorununun Cevabı" dır. Basitçe söylemek gerekirse: Her int değeri için IsNull false (0 değeri için bile) döndürür.
Ryszard Dżegan
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.