Existe-t-il une approche raisonnable des paramètres de type «par défaut» dans C # Generics?

91

Dans les modèles C ++, on peut spécifier qu'un certain paramètre de type est une valeur par défaut. Sauf indication contraire explicite, il utilisera le type T.

Cela peut-il être fait ou estimé en C #?

Je recherche quelque chose comme:

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

Pour qu'une instance du type qui ne spécifie pas explicitement T2:

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

Serait essentiellement:

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

En fin de compte, je regarde un cas dans lequel il existe un modèle assez largement utilisé, mais j'envisage de l'étendre avec un paramètre de type supplémentaire. Je pourrais sous-classer, je suppose, mais j'étais curieux de savoir s'il y avait d'autres options dans cette veine.

el2iot2
la source

Réponses:

76

Le sous-classement est la meilleure option.

Je sous-classerais votre classe générique principale:

class BaseGeneric<T,U>

avec une classe spécifique

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

Cela permet de conserver facilement votre logique au même endroit (la classe de base), mais également de fournir les deux options d'utilisation. Selon la classe, il y a probablement très peu de travail supplémentaire à faire pour y parvenir.

Reed Copsey
la source
1
ah ... ça a du sens. Le nom du type est-il autorisé à être le même si les paramètres de type fournissent une signature unique?
el2iot2
2
@ee: oui, les génériques sont overloadablepar nombre de paramètres.
Mehrdad Afshari
@ee: Oui, mais je me méfierais de faire ça. C'est «légal» dans .NET de le faire, mais cela peut prêter à confusion. Je préférerais que le nom du type dérivé de la chaîne soit similaire à la classe générique principale (il est donc évident de savoir ce qu'il est / facile à trouver), mais un nom qui indique clairement qu'il s'agit d'une chaîne.
Reed Copsey
@Reed: Cela mène-t-il vraiment à la confusion? Pour ce cas précis, je pense que le fait d'avoir le même nom aide même. Il existe des exemples dans .NET qui font la même chose: Func <> delegate par exemple.
Mehrdad Afshari
4
Par exemple, Predicate <T> est juste Func <T, bool>, mais renommé car il a un but différent.
Reed Copsey le
19

Une solution est le sous-classement. Un autre que j'utiliserais à la place, ce sont les méthodes d'usine (combinées avec le mot-clé 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>();

Dans l'exemple ci-dessus, il val2s'agit d'un type MyTemplate<int,string> et non d' un type dérivé de celui-ci.

Un type class MyStringTemplate<T>:MyTemplate<T,string>n'est pas du même type que MyTemplate<T,string>. Cela pourrait poser des problèmes dans certains scénarios. Par exemple, vous ne pouvez pas diffuser une instance de MyTemplate<T,string>to MyStringTemplate<T>.

Thanasis Ioannidis
la source
3
C'est l'approche la plus utilisable. Very nice solution
T-moty
12

vous pouvez également créer une surcharge de classe comme ceci

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

public class MyTemplate<T1> : MyTemplate<T1, string>{}
Nerdroid
la source
Veuillez lire les autres réponses avant de publier une réponse tardive, car votre solution est probablement la même que les autres.
Cheng Chen
7
la réponse acceptée était de créer une classe avec un nom différent, ma solution est de surcharger la même classe
Nerdroid
2
Non, les deux créent une nouvelle classe. Le nom n'a pas d'importance ici. MyTemplate<T1>est une classe différente de MyTemplate<T1, T2>, ni AnotherTemplate<T1>.
Cheng Chen
7
Même si les types réels sont différents, ce qui est clair et correct, le codage est plus facile si vous gardez le même nom. Il n'est pas évident pour tout le monde que la "surcharge" de classe puisse être utilisée de cette façon. @DannyChen a raison - du point de vue technique, les résultats sont les mêmes, mais cet answear est plus proche de ce que OP demandait.
Kuba
1
Malheureusement, cette solution est toujours inférieure aux vrais arguments génériques "par défaut", car un MyTemplate<T1, string>ne peut pas être assigné MyTemplate<T1>, ce qui peut être souhaitable
Felk
9

C # ne prend pas en charge une telle fonctionnalité.

Comme vous l'avez dit, vous pouvez le sous-classer (s'il n'est pas scellé et dupliquer toutes les déclarations du constructeur) mais c'est une chose complètement différente.

Mehrdad Afshari
la source
2

Malheureusement, C # ne prend pas en charge ce que vous essayez de faire. Ce serait une fonctionnalité difficile à mettre en œuvre étant donné que le type par défaut pour un paramètre devrait respecter les contraintes génériques et créerait très probablement des maux de tête lorsque le CLR tenterait d'assurer la sécurité du type.

Andrew Hare
la source
1
Pas vraiment. Cela aurait pu être fait avec un attribut (comme les paramètres par défaut dans VB.NET) et que le compilateur le remplace au moment de la compilation. La raison principale est les objectifs de conception C #.
Mehrdad Afshari
Le compilateur doit s'assurer que le paramètre par défaut satisfait les contraintes génériques. De plus, le paramètre par défaut serait une contrainte générique elle-même car toutes les hypothèses faites sur le paramètre de type dans la méthode exigeraient que tout paramètre de type autre que celui par défaut en hérite.
Andrew Hare
@Andrew, le paramètre par défaut n'a pas besoin d'être une contrainte générique. Si cela devait se comporter plus comme des paramètres de modèle par défaut en C ++, puis en étendant sur la classe d'automatonic, ce serait parfaitement bien de le faire: MyTemplate <int, float> x = null Parce que T2 n'a pas de contraintes génériques, donc float est bien malgré le type de chaîne par défaut . De cette façon, les paramètres de modèle par défaut ne sont essentiellement que du «sucre syntaxique» pour écrire MyTemplate <int> comme raccourci pour MyTemplate <int, string>.
Tyler Laing
@Andrew, je suis d'accord pour que le compilateur C # vérifie que ces paramètres de modèle par défaut satisfont les conststaints génériques existants. Le compilateur C # vérifie déjà quelque chose de similaire dans les déclarations de classes, par exemple, ajoutez une contrainte générique telle que "where U: ISomeInterface" à la classe BaseGeneric <T, U> de Reed et MyGeneric <T> échouera à compiler avec une erreur. La vérification d'un paramètre de modèle par défaut serait à peu près la même chose: le compilateur vérifie lors de la déclaration d'une classe et le message d'erreur peut être le même ou très similaire.
Tyler Laing
"Ce serait une fonctionnalité difficile à mettre en œuvre" C'est absurde. Ce serait simple à mettre en œuvre. Le travail de validation d'un type par rapport aux contraintes du modèle ne devient pas plus difficile car le type candidat apparaît à un endroit différent dans le fichier (c'est-à-dire dans le modèle plutôt qu'à l'instanciation du modèle).
Mud