web-dev-qa-db-fra.com

Redondance de déclaration C #

Lors de la déclaration d'un membre

class MyClass {

    AnyClass<WithLong<Generic,Declaration>> myProp =
        new AnyClass<WithLong<Generic,Declaration>>();

}

est assez redondant.

Nous pouvons trouver les éléments suivants, plus concis, moins bruyants:

AnyClass<WithLong<Generic,Declaration>> value = new default();
// or
AnyClass<WithLong<Generic,Declaration>> value = new();
// or
AnyClass<WithLong<Generic,Declaration>> value = new var();

Y a-t-il un avantage à la redondance réelle, ou un risque sur une déclaration plus concise que je n'ai pas identifié?

Remarque:
var est en fait un bon moyen de réduire le bruit des variables locales:

var dictionary = new AnyClass<WithLong<Generic,Declaration>>();

Ma question concerne l'extension de cette concision aux déclarations des membres, où la syntaxe var n'est pas autorisée.

7
Joseph Merdrignac

Y a-t-il un avantage à la redondance réelle, ou un risque sur une déclaration plus concise que je n'ai pas identifié?

L'équipe du langage C # a discuté de la suppression de la nécessité de cette redondance en prenant en charge l'un des exemples de syntaxes que vous citez:

class Foo
{
    private AnyClass<WithLong<Generic,Declaration>> value = new();
    ...
}

Il est actuellement en prototype et crayonné pour C # 9 .

L'équipe du langage C # est un groupe très conservateur. Ils ne modifient pas la langue sur un coup de tête et passent par une analyse rigoureuse des avantages et des inconvénients d'un changement. Le fait que ce soit arrivé à ce point comme une idée suggère fortement qu'il n'y a aucun avantage objectif à cette redondance.

Cependant, il y a des avantages subjectifs. Tout comme certains n'aiment pas utiliser var:

var value = new AnyClass<WithLong<Generic,Declaration>>();

il y aura donc des gens qui n'aiment pas non plus les nouvelles expressions de type cible. Et beaucoup de gens n'aiment pas vraiment que l'équipe ajoute de nouvelles façons de faire la même chose. L'équipe évaluera ces aversions par rapport aux avantages et pourrait encore décider de ne pas publier cette fonctionnalité. Le temps nous le dira.

8
David Arno

Les changements que vous proposez pour les variables membres ne sont qu'un changement dans le compilateur. Il ne doit y avoir aucun changement dans le CLR en cours d'exécution. Un tel changement serait donc facile à apporter. Vous devez soumettre la demande à Microsoft. J'ai également trouvé cette redondance un peu ennuyeuse (seulement légèrement). Cependant la redondance contenue dans une seule ligne me pacifie.

L'une des raisons pour lesquelles les développeurs détestent la redondance est la correction de bogues. Lorsqu'un bogue est corrigé autour du code redondant, nous pouvons alors modifier un endroit et oublier l'autre endroit. C'est un gros problème. Dans ce cas, la redondance est contenue dans une seule ligne, donc la possibilité de modifier seulement une partie de celle-ci est éloignée. Et dans la plupart de ces cas, il générera une erreur de compilation lorsqu'il sera partiellement modifié. Donc, ça va dans un certain sens.

2

L'idée centrale

Je ne vois pas de problème avec l'utilisation d'une syntaxe similaire aux variables var lors de la déclaration des variables de classe:

public class Foo
{
    var Bar = new Bar();
}

Toutes les informations nécessaires sont là, elles ne sont pas ambiguës et suivent une syntaxe connue (inférence de type variable).

Cependant, une déclaration et une initialisation combinées sont une occurrence rare pour les membres de la méthode, par opposition à la fréquence à laquelle elle se produit pour les variables. Je soupçonne que c'est pourquoi cela n'a pas vraiment été abordé par les architectes linguistiques.

Votre syntaxe proposée

Comme vous l'avez identifié, pour réduire la redondance, nous devons réduire l'une des deux références redondantes. Actuellement, seul ce type de syntaxe est autorisé (pour les variables):

var dictionary = new Dictionary<T, int>();

et pas l'un d'eux:

Dictionary<T, int> dictionary = new();
Dictionary<T, int> dictionary = new var();

À première vue, et ce que cette question tente d'affirmer, on pourrait penser que ceux-ci sont également valables.

Mais le sont-ils?

Votre exemple est légèrement limité en ce que vous ne considérez que des classes concrètes. Considérez comment cela fonctionnerait pour les interfaces (ou les classes abstraites). Premièrement, la manière actuelle actuelle:

var userService = service.Get<IUserService>();

Cela marche. Le compilateur identifie le type de retour de Get<IUserService>() puis définit var sur le même type (vraisemblablement IUserService dans cet exemple).

Mais pour votre approche suggérée:

IUserService userService = new();
IUserService userService = new var();

Cela ne fonctionne pas. Sans une instanciation d'objet concret ou une méthode avec un type de retour défini, le compilateur est incapable de déterminer exactement ce qui doit se produire ici.

Vous pouvez penser que c'est un peu triché que j'introduise la méthode de service dans l'exemple existant (puisque vous ne pouvez pas instancier une interface), mais le fait est que votre approche "type-first-var-last" ne le fait tout simplement pas ajouter toute réduction à ce cas, car il se traduirait par:

IUserService userService = service.Get<IUserService>();

= pas de raccourcissement.

Cela nous amène à la conclusion simple que l'approche existante permet une syntaxe raccourcie mais cohérente dans tous les cas, alors que vos suggestions seraient incohérentes ou inapplicables selon que le type utilisé est concret ou non.

En d'autres termes, votre suggestion n'est tout simplement pas aussi bonne que l'approche existante car elle ne peut pas couvrir tous les cas pertinents.

L'approche default

Dans ce qui précède, j'ai omis votre première suggestion:

Dictionary<T, int> dictionary = new default();

J'éviterais cela en général, car default a un comportement défini. Pour les types de référence, default(T) renvoie spécifiquement null.

Quand j'ai lu cette suggestion, ma première conclusion était que vous affectiez null à la variable. Bien que votre syntaxe soit subtilement différente (aucun paramètre de type utilisé), je m'oppose à ce que ces comportements très différents soient tous deux implémentés sur le même mot clé default.

Techniquement, cela pourrait être fait comme vous le souhaitez. Mais cela va avoir un impact négatif sur la lisibilité, donc je n'aime pas ça.

1
Flater

Pour vraiment répondre à cette question, vous devez comprendre le fonctionnement des compilateurs et des éditeurs de liens. C # fait essentiellement ces deux choses en une seule étape. La différence dans le contexte de la définition des variables fait toute la différence quant à la faisabilité de ce que vous demandez.

Au niveau mondial, vous devez définir la taille et la forme de votre classe. Les champs, méthodes, etc. doivent être explicites et bien définis. Ces informations sont généralement mises en cache afin que lorsque le compilateur travaille sur les implémentations de méthode, il puisse référencer cette structure de définition de classe.

L'un des problèmes clés ici est que vous devez connaître le type de main gauche au niveau de la classe . C'est l'une des principales raisons pour lesquelles var fonctionne même du tout. Nous ne pouvons donc pas utiliser var comme type de retour ou déclaration de champ de classe.

Donc, si nous regardons à droite, vous avez besoin d'un moyen raisonnable d'identifier le type à créer. Étant donné que le type de main gauche peut être un interface (et dans mon code, il s'agit souvent d'une interface), nous ne pouvons pas simplement compter sur un raccourci new().

La solution fournie par Java 8 était de simplifier la spécification générique, mais cela est possible car les génériques Java sont un contrat faible contrairement aux génériques Microsoft. La déclaration ressemble à ceci:

public class MyObject {
    private Map<String,List<MyObject>> cache = new HashMap<>();
}

Cela pourrait être "assez bon" dans votre scénario. Pour traduire ce code en C #, cela ressemblerait à ceci:

public class MyObject
{
    private IDictionary<string,IList<MyObject>> cache = new Dictionary<>();
}

Il réduit la redondance gênante dans la déclaration générique, mais fournit l'expressivité pour choisir une implémentation autre que celle définie dans la définition de type pour le champ.

Il semble qu'il existe une proposition pour une nouvelle version de C # pour résoudre ce problème, il sera donc intéressant de voir si/comment Microsoft répond à cette préoccupation. Merci à JacquesB pour le référence .

0
Berin Loritsch