2012-10-30 18 views
8

Załóżmy, że mam następujące klasy:Zrozumienie generycznych C# i typu wartości Nullable. Zwróci null lub zerowalne

public class GenericClass<T> 
{ 
    public T Find() 
    { 
     //return T if found otherwise null or Nullable<T> 
    } 
} 

Gdzieś Chciałbym specjalizować moją klasę używając T z class, innym razem z struct. Mam do czynienia z tym issue: Nie mogę zwrócić Nullable<T>, jeśli typ T nie jest ograniczony do struct.

Chciałbym zapewnić realizację mojego sposobu Find który działa jeśli T specjalizuje się zarówno class lub struct. W przypadku niepowodzenia Find, chciałbym zwrócić null, jeśli T jest klasą inaczej Nullable<T>.

Czy to możliwe bez użycia odbicia? Jeśli tak, to w jaki sposób?

+0

nie można powrócić T lub pustych jeśli typ zwracany jest T. – fsimonazzi

Odpowiedz

15

Możesz zwrócić default(T).

Dla klasy będzie to null. Dla każdego Nullable<T> będzie to Nullable<T> bez wartości (skutecznie null).

Biorąc to pod uwagę, jeśli używasz to z struct a nie Nullable<T> jako typ, default(T) będzie wartość domyślna struktury.


Jeśli chcesz, aby tę pracę jednolicie dla każdej klasy lub struktury, to prawdopodobnie trzeba zwracać dwie wartości - można użyć ramy jako inspiracji tutaj i mieć metodę TryXXX, tj:

public bool TryFind(out T) 

Następnie można użyć default(T), gdy wartość nie zostanie znaleziona, i zwróci wartość false. Pozwala to uniknąć konieczności stosowania typów null. Można również napisać to zwracanie Tuple<bool, T> lub podobny, jeśli chciał uniknąć parametr out, czyli:

public Tuple<bool, T> Find() 

Ostateczna opcja potencjalnie byłoby dokonać klasa non-generic, a następnie użyć parę ogólne metody:

class YourClass // Non generic 
{ 
    public T FindReference<T>() where T : class 
    { 
     // ... 
     return null; 
    } 

    public Nullable<T> FindValue<T>() where T : struct 
    { 
     // ... 
     return default(T); 
    } 
} 

Należy pamiętać, że potrzebne są różne nazwy, ponieważ nie można mieć przeciążonej metody wyłącznie na podstawie typu zwrotu.

+0

Technicznie 'default (T)' nie zwróci 'pustych ' kiedy 'int' jest dostarczany jako ogólny argument, tak jak OP chce, ale to zdecydowanie daje ci większość drogi. Doprawdy, jedynym sposobem uzyskania dokładnie tego, co chce OP, jest dostarczenie dwóch ogólnych implementacji, jednej dla typów wartości i jednej dla typów odniesienia. – Tejs

+0

@Tejs. więc jeśli muszę specjalizować T z typem numeru, czy jestem zmuszony użyć parametru out, aby osiągnąć ogólne rozwiązanie? Co domyślnie zwraca, jeśli T jest int? – Heisenbug

+0

@Tejs Tak - to właśnie próbowałem powiedzieć w ostatnim zdaniu - 'default (T)' dostarczy 'Nullable ' jeśli użyjesz 'Nullable ' jako typowego, thoguh. –

2

użyłbym następujące rozwiązanie:

public Boolean Find(out T result) 
{ 
    // Pseudo code 
    if (FindItem == true) 
    { 
     result = ItemFound; 
     return true; 
    } 
    result = default(T); 
    return false; 
} 

Dlaczego? Ponieważ Nullable<T> akceptuje tylko struct, a powyższa metoda obsługuje zarówno klasy, jak i struktury.

+1

+1, a ten idiom jest zwykle nazywany 'TryXXXX', więc" TryGetValue "lub" TryFind "będą odpowiednie. – user7116

+0

@sletterlettervariables Tak, masz rację. Zostałem jednak przy nazwisku, którego używał Heisenburg. –

0

Jak powiedział Reed, możesz zwrócić default(T).

Moim zdaniem ma to jedną wielką wadę: Jeśli twoja metoda zwróci default(T), jeśli przedmiot nie zostanie znaleziony, utracisz możliwość zwracania wartości domyślnych dla typów wartości (np.zwracanie 0 dla Find<int> może być ogólnie poprawną wartością zwracaną).

wolałbym pójść na coś takiego

public class GenericClass<T> 
{ 
    public Option<T> Find() 
    { 
     //return Option.Some(item) if found otherwise Option.None<T>() 
    } 
} 

public static class Option 
{ 
    public static Option<T> None<T>() 
    { 
     return new Option<T>(default(T), false); 
    } 

    public static Option<T> Some<T>(T value) 
    { 
     return new Option<T>(value, true); 
    } 
} 

public sealed class Option<T> 
{ 
    private readonly T m_Value; 
    private readonly bool m_HasValue; 

    public void Option(T value, bool hasValue) 
    { 
     m_Value = value; 
     m_HasValue = hasValue; 
    } 

    public bool HasValue 
    { 
     get { return m_HasValue; } 
    }   

    public T Value 
    { 
     get { return m_Value; } 
    } 
} 
+1

To skutecznie pisze nową formę 'Nullable ', która nie jest ograniczona do struktur - teraz, jeśli C# miało dopasowanie do wzorca, to byłby to sposób na wykonanie;) –