Esiste un approccio ragionevole ai parametri di tipo "predefinito" in Generics C #?


91

Nei modelli C ++, è possibile specificare che un determinato parametro di tipo è un valore predefinito. Ad esempio, a meno che non sia specificato esplicitamente, utilizzerà il tipo T.

Può essere fatto o approssimato in C #?

Sto cercando qualcosa come:

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

In modo che un'istanza del tipo che non specifica esplicitamente T2:

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

Sarebbe essenzialmente:

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

In definitiva, sto esaminando un caso in cui esiste un modello che è abbastanza ampiamente utilizzato, ma sto considerando di espandere con un parametro di tipo aggiuntivo. Potrei sottoclassare, immagino, ma ero curioso se ci fossero altre opzioni in questo senso.

Risposte:


76

La sottoclasse è l'opzione migliore.

Sottoclassi la tua classe generica principale:

class BaseGeneric<T,U>

con una classe specifica

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

Ciò rende facile mantenere la logica in un posto (la classe base), ma anche facile fornire entrambe le opzioni di utilizzo. A seconda della classe, probabilmente è necessario molto poco lavoro extra per farlo accadere.


1
ah ... ha senso. Il nome del tipo può essere lo stesso se i parametri del tipo forniscono una firma univoca?
el2iot2

2
@ee: sì, i generici dipendono overloadabledal conteggio dei parametri.
Mehrdad Afshari

@ee: Sì, ma sarei cauto nel farlo. È "legale" in .NET farlo, ma può creare confusione. Preferirei che il nome del tipo derivato dalla stringa fosse simile alla classe generica principale (quindi è ovvio cosa è / facile da trovare), ma un nome che renda ovvio che si tratta di una stringa.
Reed Copsey

@Reed: porta davvero a confusione? Per questo caso specifico, penso che avere lo stesso nome aiuti anche. Ci sono esempi in .NET che fanno la stessa cosa: Func <> delegate per esempio.
Mehrdad Afshari

4
Ad esempio, Predicate <T> è solo Func <T, bool>, ma rinominato poiché ha uno scopo diverso.
Reed Copsey

19

Una soluzione è la sottoclasse. Un altro che userei invece, sono i metodi di fabbrica (combinati con la parola chiave 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>();

Nell'esempio sopra val2è di tipo MyTemplate<int,string> e non un tipo derivato da esso.

Un tipo class MyStringTemplate<T>:MyTemplate<T,string>non è lo stesso tipo di MyTemplate<T,string>. Ciò potrebbe porre alcuni problemi in determinati scenari. Ad esempio, non puoi lanciare un'istanza di MyTemplate<T,string>to MyStringTemplate<T>.


3
Questo è l'approccio più utilizzabile. Soluzione molto bella
T-moty

12

puoi anche creare una classe Overload in questo modo

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

public class MyTemplate<T1> : MyTemplate<T1, string>{}

Per favore leggi altre risposte prima di pubblicare una risposta in ritardo, perché probabilmente la tua soluzione è la stessa delle altre.
Cheng Chen

7
la risposta accettata era creare una classe con un nome diverso, la mia soluzione è sovraccaricare la stessa classe
Nerdroid

2
No, entrambi stanno creando una nuova classe. Il nome non ha importanza qui. MyTemplate<T1>è una classe diversa da MyTemplate<T1, T2>nessuno dei due AnotherTemplate<T1>.
Cheng Chen

7
Anche se i tipi reali sono diversi, il che è chiaro e corretto, la codifica è più semplice se si mantiene lo stesso nome. Non è ovvio per tutti che la classe "sovraccarico" possa essere usata in questo modo. @DannyChen ha ragione: dal punto di vista tecnico i risultati sono gli stessi, ma questa risposta è più vicina a ottenere ciò che OP ha chiesto.
Kuba

1
Sfortunatamente questa soluzione è ancora inferiore ai veri argomenti generici "predefiniti", perché MyTemplate<T1, string>non può essere assegnato a MyTemplate<T1>, il che può essere desiderabile
Felk

9

C # non supporta questa funzionalità.

Come hai detto, puoi sottoclassarlo (se non è sigillato e duplicare tutte le dichiarazioni del costruttore) ma è una cosa completamente diversa.


2

Sfortunatamente C # non supporta ciò che stai cercando di fare. Sarebbe una caratteristica difficile da implementare dato che il tipo predefinito per un parametro dovrebbe aderire ai vincoli generici e molto probabilmente creerebbe grattacapi quando il CLR cerca di garantire l'indipendenza dai tipi.


1
Non proprio. Avrebbe potuto essere fatto con un attributo (come i parametri predefiniti in VB.NET) e fare in modo che il compilatore lo sostituisse in fase di compilazione. Il motivo principale sono gli obiettivi di progettazione di C #.
Mehrdad Afshari

Il compilatore deve assicurarsi che il parametro predefinito soddisfi i vincoli generici. Anche il parametro predefinito sarebbe esso stesso un vincolo generico perché qualsiasi ipotesi fatta sul parametro di tipo nel metodo richiederebbe che qualsiasi parametro di tipo non predefinito erediti da esso.
Andrew Hare

@Andrew, il parametro predefinito non deve essere un vincolo generico. Se questo dovesse comportarsi più come i parametri del modello predefinito in C ++, quindi estendersi alla classe di automatonic andrebbe benissimo farlo: MyTemplate <int, float> x = null Perché T2 non ha vincoli generici, quindi float va bene nonostante il tipo di stringa predefinito . In questo modo i parametri predefiniti del modello sono essenzialmente solo "zucchero sintattico" per scrivere MyTemplate <int> come scorciatoia per MyTemplate <int, string>.
Tyler Laing

@Andrew, sono d'accordo che il compilatore C # dovrebbe verificare che tali parametri del modello predefinito soddisfino i costanti generici esistenti. Il compilatore C # verifica già qualcosa di simile nelle dichiarazioni di classe, ad esempio aggiungi un vincolo generico come "where U: ISomeInterface" alla classe BaseGeneric <T, U> di Reed e quindi MyGeneric <T> non verrà compilato con un errore. La verifica di un parametro di modello predefinito sarebbe praticamente la stessa: il compilatore verifica sulla dichiarazione di una classe e il messaggio di errore può essere lo stesso o molto simile.
Tyler Laing

"Sarebbe una caratteristica difficile da implementare" Non ha senso. Sarebbe banale da implementare. Il compito di convalidare un tipo rispetto ai vincoli del modello non diventa più difficile perché il tipo candidato appare in una posizione diversa nel file (cioè nel modello piuttosto che durante l'istanza del modello).
Mud
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.