Czy istnieje rozsądne podejście do „domyślnych” parametrów typu w C # rodzajach generycznych?

93

W szablonach C ++ można określić, że określony parametr typu jest domyślny. To znaczy, o ile nie zostanie to wyraźnie określone, użyje typu T.

Czy można to zrobić lub przybliżyć w C #?

Szukam czegoś takiego:

public class MyTemplate<T1, T2=string> {}

Tak, że instancja typu, który nie określa jawnie T2:

MyTemplate<int> t = new MyTemplate<int>();

Byłoby zasadniczo:

MyTemplate<int, string> t = new MyTemplate<int, string>();

Ostatecznie patrzę na przypadek, w którym istnieje szablon, który jest dość szeroko stosowany, ale rozważam rozszerzenie o dodatkowy parametr typu. Chyba mógłbym podklasować, ale byłem ciekawy, czy w tym duchu są inne opcje.

el2iot2
źródło

Odpowiedzi:

77

Podklasy to najlepsza opcja.

Podklasowałbym twoją główną klasę ogólną:

class BaseGeneric<T,U>

z określoną klasą

class MyGeneric<T> : BaseGeneric<T, string>

Ułatwia to utrzymanie logiki w jednym miejscu (klasie bazowej), ale także zapewnia łatwe zapewnienie obu opcji użycia. W zależności od klasy prawdopodobnie potrzeba bardzo niewiele dodatkowej pracy, aby to się stało.

Reed Copsey
źródło
1
ach ... to ma sens. Czy nazwa typu może być taka sama, jeśli parametry typu zapewniają unikatowy podpis?
el2iot2
2
@ee: tak, typy generyczne są overloadablewedług liczby parametrów.
Mehrdad Afshari
@ee: Tak, ale byłbym ostrożny. W .NET jest to „legalne”, ale może prowadzić do nieporozumień. Wolałbym, aby nazwa typu pochodnego typu string była podobna do głównej klasy generycznej (więc jest oczywiste, co to jest / łatwe do znalezienia), ale nazwa, która sprawia, że ​​jest oczywiste, że jest to ciąg.
Reed Copsey
@Reed: Czy to naprawdę prowadzi do zamieszania? W tym konkretnym przypadku myślę, że posiadanie tej samej nazwy nawet pomaga. Istnieją przykłady w .NET, które robią to samo: na przykład delegat Func <>.
Mehrdad Afshari
4
Na przykład Predicate <T> to po prostu Func <T, bool>, ale zmieniono jego nazwę, ponieważ służy do innego celu.
Reed Copsey
19

Jednym z rozwiązań jest tworzenie podklas. Innym, którego użyłbym zamiast tego, są metody fabryczne (w połączeniu ze słowem kluczowym var).

public class MyTemplate<T1,T2>
{
     public MyTemplate(..args..) { ... } // constructor
}

public static class MyTemplate{

     public static MyTemplate<T1,T2> Create<T1,T2>(..args..)
     {
         return new MyTemplate<T1, T2>(... params ...);
     }

     public static MyTemplate<T1, string> Create<T1>(...args...)
     {
         return new MyTemplate<T1, string>(... params ...);
     }
}

var val1 = MyTemplate.Create<int,decimal>();
var val2 = MyTemplate.Create<int>();

W powyższym przykładzie val2jest to typ, MyTemplate<int,string> a nie typ wyprowadzony z niego.

Typ class MyStringTemplate<T>:MyTemplate<T,string>nie jest tym samym typem, co MyTemplate<T,string>. Może to spowodować pewne problemy w niektórych scenariuszach. Na przykład nie możesz przesłać instancji MyTemplate<T,string>do MyStringTemplate<T>.

Thanasis Ioannidis
źródło
3
To najbardziej użyteczne podejście. Bardzo fajne rozwiązanie
T-moty
12

możesz także stworzyć taką klasę Overload

public class MyTemplate<T1, T2> {
    public T1 Prop1 { get; set; }
    public T2 Prop2 { get; set; }
}

public class MyTemplate<T1> : MyTemplate<T1, string>{}
Nerdroid
źródło
Przeczytaj inne odpowiedzi, zanim opublikujesz późną odpowiedź, ponieważ prawdopodobnie Twoje rozwiązanie jest takie samo, jak inne.
Cheng Chen
7
zaakceptowaną odpowiedzią było utworzenie klasy o innej nazwie, moim rozwiązaniem jest Przeciążenie tej samej klasy
Nerdroid
2
Nie, obaj tworzą nową klasę. Nazwa nie ma tutaj znaczenia. MyTemplate<T1>jest inną klasą MyTemplate<T1, T2>, ani też AnotherTemplate<T1>.
Cheng Chen
7
Nawet jeśli rzeczywiste typy są różne, co jest jasne i poprawne, kodowanie jest łatwiejsze, jeśli zachowasz tę samą nazwę. Nie dla każdego jest oczywiste, że „przeciążenie” klasy można wykorzystać w ten sposób. @DannyChen ma rację - z technicznego punktu widzenia wyniki są takie same, ale ta odpowiedź jest bliższa osiągnięcia tego, o co prosił OP.
Kuba
1
Niestety to rozwiązanie jest nadal gorsze od prawdziwych "domyślnych" ogólnych argumentów, ponieważ MyTemplate<T1, string>nie można przypisać do MyTemplate<T1>, co może być pożądane
Felk
10

C # nie obsługuje takiej funkcji.

Jak powiedziałeś, możesz podklasować go (jeśli nie jest zapieczętowany i zduplikować wszystkie deklaracje konstruktora), ale to zupełnie inna sprawa.

Mehrdad Afshari
źródło
3

Niestety C # nie obsługuje tego, co próbujesz zrobić. Byłaby trudna do zaimplementowania, biorąc pod uwagę, że domyślny typ parametru musiałby być zgodny z ogólnymi ograniczeniami i najprawdopodobniej spowodowałby ból głowy, gdy CLR próbowałby zapewnić bezpieczeństwo typu.

Andrew Hare
źródło
1
Nie całkiem. Można było to zrobić z atrybutem (takim jak domyślne parametry w VB.NET) i kazać kompilatorowi zastąpić go w czasie kompilacji. Głównym powodem są cele projektowe C #.
Mehrdad Afshari
Kompilator musi zapewnić, że parametr domyślny spełnia ograniczenia ogólne. Również parametr domyślny byłby samym ograniczeniem ogólnym, ponieważ wszelkie założenia dotyczące parametru typu w metodzie wymagałyby, aby każdy parametr typu innego niż domyślny dziedziczył po nim.
Andrew Hare
@Andrew, parametr domyślny nie musi być ograniczeniem ogólnym. Gdyby to zachowywało się bardziej jak domyślne parametry szablonu w C ++ niż rozszerzanie na klasę automatu, byłoby w porządku: MyTemplate <int, float> x = null Ponieważ T2 nie ma ogólnych ograniczeń, więc float jest w porządku pomimo domyślnego typu łańcucha . W ten sposób domyślne parametry szablonu są po prostu „cukrem syntaktycznym” do pisania MyTemplate <int> jako skrótu dla MyTemplate <int, string>.
Tyler Laing,
@ Andrew, zgadzam się, że kompilator C # powinien sprawdzić, czy takie domyślne parametry szablonu spełniają istniejące ogólne ograniczenia. Kompilator C # już weryfikuje coś podobnego w deklaracjach klas, np. Dodaje ogólne ograniczenie, takie jak „where U: ISomeInterface” do klasy Reed's BaseGeneric <T, U>, a następnie MyGeneric <T> nie uda się skompilować z błędem. Weryfikacja domyślnego parametru szablonu byłaby bardzo podobna: kompilator weryfikuje deklarację klasy i komunikat o błędzie może być taki sam lub bardzo podobny.
Tyler Laing,
„Trudno byłoby zaimplementować tę funkcję” To nonsens. Wdrożenie byłoby trywialne. Zadanie walidacji typu pod kątem ograniczeń szablonu nie staje się trudniejsze, ponieważ typ kandydata pojawia się w innym miejscu w pliku (tj. W szablonie, a nie podczas tworzenia szablonu).
Błoto