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.

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.


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>.


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>{}

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.


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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.