2013-02-15 29 views
14

szukam użytkowania próbka coś takiego:Constrain generic być pustych type

Foo<string> stringFoo = new Foo<string>("The answer is"); 
Foo<int> intFoo = new Foo<int>(42); 
// The Value of intFoo & stringFoo are strongly typed 
stringFoo.Nullify(); 
intFoo.Nullify(); 
if (stringFoo == null && intFoo == null) 
    MessageBox.Show("Both are null); 

Biorąc pod uwagę to klasa Foo, mogę auto-wrap T do Nullable:

public class Foo1<T> 
    where T : struct 
{ 
    private T? _value; 

    public Foo(T? initValue) 
    { 
     _value = initValue; 
    } 

    public T? Value { get { return _value; } } 

    public void Nullify { _value = null; } 

} 

Działa to dla prymitywów, ale nie dla String lub innych klas.

Następny smak działa na smyczki, ale nie prymitywy:

public class Foo2<T> 
{ 
    private T _value; 

    public Foo(T initValue) 
    { 
     _value = initValue; 
    } 

    public T Value { get { return _value; } } 

    public void Nullify { _value = default(T); } 

} 

mogę użyć Nullable<int> dla foo2 a kod będzie działać tak:

Foo2<int?> intFoo = new Foo<int?>(42); 

Ale ten jest podatny na błędy, ponieważ nie dla Foo2. Jeśli mógłbym ograniczyć T do typów, które wspierają zerowalność, to byłoby dobrze.

Więc po tym wszystkim, czy jest jakiś sposób, aby ograniczyć T, aby być typu pustych?

Niektóre dodatkowe uwagi: .NET 4.0, VS2010. I znalazłem tu jedno podobne pytanie, ale bez udanej odpowiedzi.

Odpowiedz

9

Możesz być w stanie dokonać konstruktora Foo<T> wewnętrzny i wymagają, że nowe przypadki mogą być tworzone tylko przez klasy fabrycznej:

public class Foo<T> 
{ 
    private T value; 
    internal Foo(T value) 
    { 
     this.value = value; 
    } 

    public void Nullify() 
    { 
     this.value = default(T); 
    } 

    public T Value { get { return this.value; } } 
} 

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

    public static Foo<T?> Create<T>(T? value) where T : struct 
    { 
     return new Foo<T?>(value); 
    } 
} 
+0

Interesujące. W prawdziwym życiu Foo jest bardziej skomplikowany i jest automatycznie tworzony przez framework. Będę musiał sprawdzić, czy istnieje sposób podpięcia/dostosowania sposobu, w jaki obiekty są tworzone, aby to podejście było wykonalne. – tcarvin

+0

Zaakceptowany, ponieważ daje najmniejsze zaskoczenie klientowi wewnętrznemu biblioteki (czas projektowania/kompilacji w porównaniu z czasem wykonania). Ale test użyty przez @JonSkeet też był świetny! – tcarvin

17

Nie ma ograniczenia można ubiegać się o to, ale może sprawdzianem dla niego w czasie wykonywania:

if (default(T) != null) 
{ 
    throw new SomeAppropriateException(typeof(T) + " is not a nullable type"); 
} 

można nawet umieścić, że w statycznym konstruktorze, który będzie upewnić się, że tylko stracony raz za skonstruowanego typu - i każdego, kto próbuje wykorzystać Foo<int> gdziekolwiek będzie mieć twardy czas ignorując TypeInitializerException. To nie jest strasznie przyjazny dla publicznej API, ale myślę, że to rozsądne dla jednego wewnętrznego.

EDIT: Jest jeden straszne sposób utrudniając tworzyć instancje Foo<int> ... można użyć (zasady korzystania rozdzielczości przeciążenie wraz z domyślnymi parametrami i kilka ograniczonych typów generycznych) ghastly code in this blog post i zaznaczyć przeciążenie, które ma na celu uzyskanie wartości zerowej, która jest przestarzała i zawiera błąd. W ten sposób Foo<int> nadal będzie prawidłowym typem, ale trudno będzie utworzyć jego instancję. I nie zamierzam zaleca się to zrobić chociaż ...

+0

@NikolayKhil: Komentarz, na który odpowiadasz, zniknął, ale kod w tej odpowiedzi * działa * dla 'int?', Ponieważ sprawdzanie nieważności "działa prawidłowo" dla argumentów typu zerowego. –

+0

Czy jestem zły, czy też typy z pustym hasłem to wszystkie elementy? Chodzi mi o to, że nie ma ograniczeń dla "typów nullable", ale istnieje ograniczenie dla struct. Ale zrobiłem mój własny test i jeśli, jeśli podasz T: struct constraint do jakiegoś ogólnego parametru, później nie będziesz mógł użyć typu zerowego jako ogólnego argumentu. Na przykład X nie będzie działać z tym ograniczeniem. –

+0

Oczywiście dlatego, że int? jest Nullable , a Nullable jest typem referencyjnym, ale być może .NET devteam mógł stworzyć rodzaj syntaktycznego cukru, który pozwoliłby na użycie tego przypadku. –

0

nie podoba mi się to tak bardzo jak składni foo1, ale tutaj jest foo3:

public class Foo3<T> 
    where T : struct 
{ 

    private T _value; 
    private T _nullValue; 

    public Foo3(T initValue) 
     : this(initValue, default(T)) 
    { 
    } 

    public Foo3(T initValue, T nullValue) 
    { 
     _value = initValue; 
     _nullValue = nullValue; 
    } 

    public T Value { get { return _value; } } 

    public bool IsNull 
    { 
     get 
     { 
      return object.Equals(_value, _nullValue); 
     } 
    } 

    public void Nullify() { _value = _nullValue; } 

} 

I wtedy mój zwyczaj staje:

Foo3<string> stringFoo = new Foo<string>("The answer is"); 
Foo3<int> intFoo = new Foo<int>(42, int.MinValue); 
stringFoo.Nullify(); 
intFoo.Nullify(); 
if (stringFoo.IsNull && intFoo.IsNull) 
    MessageBox.Show("Both are null); 

To wciąż podatne na błędy, ponieważ coraz właściwość wartość foo3 (i foo2) nie jest proste. Foo1 był najlepszy, ponieważ automatycznie pakował wartość zerową.

Być może potrzebuję ValueTypeFoo i ObjectFoo i poradzę sobie z dwoma wersjami.