Pourquoi y a-t-il une nouvelle contrainte () en C # mais aucune autre contrainte similaire?

19

Dans les génériques C #, nous pouvons déclarer une contrainte pour qu'un paramètre de type Tait un constructeur par défaut, en disant where T : new(). Cependant, aucun autre type de contrainte comme celui-ci n'est valide - new(string)par exemple, etc.

Du point de vue de la conception et / ou de la mise en œuvre d'un langage, quelle en est la raison?

Y a-t-il quelque chose dans la façon dont les constructeurs fonctionnent ou dans la façon dont le système de type est implémenté qui l'interdit (ou du moins le rend plus difficile)? Si oui, c'est quoi? Je me souviens avoir lu quelque part qui default(T)compile réellement new T()pour T : struct. Est-ce lié à cela, peut-être?

Ou s'agit-il simplement d'une décision de conception prise pour éviter de compliquer le langage?

Theodoros Chatzigiannakis
la source
new(string)n'est pas une contrainte de constructeur par défaut. Votre question revient à dire "Pourquoi n'y a-t-il pas des contraintes qui nécessitent des signatures de constructeur spécifiques?" Très probablement parce qu'une telle contrainte ne serait pas particulièrement utile.
Robert Harvey
3
@RobertHarvey Oui, c'est exactement ce que je dis. Je demande essentiellement s'il y a quelque chose qui rend les constructeurs par défaut spéciaux au niveau de l'implémentation ou si c'était juste un choix arbitraire d'inclure cela et pas l'autre.
Theodoros Chatzigiannakis
Les constructeurs par défaut sont utiles de certaines manières spécifiques et importantes. Par exemple, ils rendent un type facilement sérialisable.
Robert Harvey
6
Des signatures de ctor spécifiques pourraient être utiles. Par exemple, si votre variable de type est contrainte d'être de Collection <T> avec un ctor de T (Collection <T>), vous savez que vous pouvez construire de nouvelles collections à partir d'une autre. Mais si cette utilité vaut la complexité supplémentaire, c'est une question.
Phoshi

Réponses:

5

Pour une réponse faisant autorité, je vous renvoie à la réponse d'Eric Lippert à cette question sur StackOverflow il y a quelques années, dont un extrait est brièvement cité ci-dessous.

Cependant, dans ce cas spécifique, je peux certainement vous donner quelques raisons pour lesquelles je repousserais la fonctionnalité si elle apparaissait lors d'une réunion de conception en tant que fonctionnalité possible pour une future version du langage.

...

Je dis que nous devrions soit faire toute la fonctionnalité, soit ne pas le faire du tout. S'il est important de pouvoir restreindre les types pour avoir des constructeurs particuliers, alors faisons toute la fonctionnalité et restreignons les types sur la base des membres en général et pas seulement des constructeurs.

Chris Hannon
la source
2
Cette citation, prise hors contexte, est très trompeuse. Cela suggère qu'Eric pensait que Microsoft aurait dû "aller jusqu'au bout", ce qui n'est pas du tout sa position. Il est, en fait, carrément contre la fonctionnalité proposée.
Robert Harvey
1
Merci, cela ne répond que partiellement à ma question: Vers la fin de sa réponse, Eric Lippert mentionne que c'est une limitation de l'IL et que l'inclusion de cette fonctionnalité nécessiterait un ajout à l'IL. Ce serait parfait si vous pouviez fournir une source sur quel IL est généré pour la partie qui est implémentée - c'est-à-dire appeler génériquement un constructeur par défaut.
Theodoros Chatzigiannakis
@TheodorosChatzigiannakis: Pourquoi ne lancez-vous pas le décompilateur de Telerik et découvrez par vous-même?
Robert Harvey
2
@RobertHarvey Je ne pense pas que cela suggère une position pour lui du tout, mais j'ai inclus une citation supplémentaire pour souligner sa position.
Chris Hannon
1
Hmm, F # a des méthodes plus avancées pour contraindre un type , comme vérifier au moment de la compilation si une classe a un opérateur. Peut-être, F # a besoin d'un système de contraintes plus puissant que C # en raison de sa vérification de type très stricte. Néanmoins, ce langage est capable d'implémenter des méthodes avancées pour limiter la classe sur .Net Framework.
OnesimusUnbound
16

La décompilation (selon la suggestion de Robert Harvey) a produit ce qui suit, pour toute personne intéressée. Cette méthode:

static T GenericMake<T>()
    where T : new()
{
    return new T();
}

Apparemment, une fois compilé, cela devient:

private static T GenericMake<T>()
    where T : new()
{
    T t;
    T t1 = default(T);
    if (t1 == null)
    {
        t = Activator.CreateInstance<T>();
    }
    else
    {
        t1 = default(T);
        t = t1;
    }
    return t;
}
  • Si Test un type de valeur, new()devient default(T).
  • Si Test un type de référence, new()fonctionne en utilisant la réflexion. Activator.CreateInstance()appels internes RuntimeType.CreateInstanceDefaultCtor().

Donc, il est là - en interne, les constructeurs par défaut sont vraiment spéciaux pour C # par rapport au CLR. Donner à d'autres constructeurs le même traitement aurait été coûteux, même s'il existe des cas d'utilisation valables pour des contraintes plus complexes dans les génériques.

Theodoros Chatzigiannakis
la source
4
Intéressant. Pourquoi l'appel en double default(T) pour les types de valeur?
Avner Shahar-Kashtan
2
@ AvnerShahar-Kashtan Je ne sais pas. Il peut s'agir d'un artefact du processus de compilation / décompilation, comme la tvariable (qui pourrait être remplacée par des returninstructions imbriquées ).
Theodoros Chatzigiannakis