web-dev-qa-db-fra.com

Pourquoi devrais-je éviter d'utiliser les propriétés en C #?

Dans son excellent livre, CLR Via C #, Jeffrey Richter a dit qu'il n'aime pas les propriétés et recommande de ne pas les utiliser. Il a donné une raison, mais je ne comprends pas vraiment. Quelqu'un peut-il m'expliquer pourquoi je devrais ou ne devrais pas utiliser des propriétés? En C # 3.0, avec des propriétés automatiques, cela change-t-il?

Pour référence, j'ai ajouté les opinions de Jeffrey Richter:

• Une propriété peut être en lecture seule ou en écriture seule; l'accès aux champs est toujours lisible et accessible en écriture. Si vous définissez une propriété, il est préférable d'offrir à la fois les méthodes d'accesseur get et set.

• Une méthode de propriété peut lever une exception; l'accès aux champs ne lève jamais d'exception.

• Une propriété ne peut pas être transmise en tant que paramètre out ou ref à une méthode; un champ peut. Par exemple, le code suivant ne sera pas compilé:

using System;
public sealed class SomeType
{
   private static String Name 
   {
     get { return null; }
     set {}
   }
   static void MethodWithOutParam(out String n) { n = null; }
   public static void Main()
   {
      // For the line of code below, the C# compiler emits the following:
      // error CS0206: A property or indexer may not
      // be passed as an out or ref parameter
      MethodWithOutParam(out Name);
   }
}

• Une méthode de propriété peut être longue à exécuter; l'accès aux champs se termine toujours immédiatement. Une raison courante d'utiliser des propriétés est d'effectuer la synchronisation des threads, ce qui peut arrêter le thread pour toujours, et par conséquent, une propriété ne doit pas être utilisée si la synchronisation des threads est requise. Dans cette situation, une méthode est préférée. En outre, si votre classe est accessible à distance (par exemple, votre classe est dérivée de System.MashalByRefObject), l'appel de la méthode de la propriété sera très lent et, par conséquent, une méthode est préférée à une propriété. À mon avis, les classes dérivées de MarshalByRefObject ne devraient jamais utiliser de propriétés.

• Si elle est appelée plusieurs fois de suite, une méthode de propriété peut renvoyer une valeur différente à chaque fois; un champ renvoie la même valeur à chaque fois. La classe System.DateTime possède une propriété Now en lecture seule qui renvoie la date et l'heure actuelles. Chaque fois que vous interrogez cette propriété, elle renvoie une valeur différente. Il s'agit d'une erreur, et Microsoft souhaite pouvoir corriger la classe en faisant de Now une méthode au lieu d'une propriété.

• Une méthode des propriétés peut provoquer des effets secondaires observables; l'accès aux champs ne le fait jamais. En d'autres termes, un utilisateur d'un type doit être en mesure de définir diverses propriétés définies par un type dans l'ordre qu'il choisit sans remarquer de comportement différent dans le type.

• Une méthode de propriété peut nécessiter de la mémoire supplémentaire ou renvoyer une référence à quelque chose qui ne fait pas réellement partie de l'état de l'objet, donc la modification de l'objet renvoyé n'a aucun effet sur l'objet d'origine; l'interrogation d'un champ renvoie toujours une référence à un objet qui est garanti de faire partie de l'état de l'objet d'origine. Travailler avec une propriété qui renvoie une copie peut être très déroutant pour les développeurs, et cette caractéristique n'est souvent pas documentée.

102
Quan Mai

La raison pour laquelle Jeff n'aime pas les propriétés est qu'elles ressemblent à des champs - donc les développeurs qui ne comprennent pas la différence les traiteront comme s'ils étaient des champs, en supposant qu'ils seraient bon marché à exécuter, etc.

Personnellement, je ne suis pas d'accord avec lui sur ce point particulier - je trouve que les propriétés rendent le code client beaucoup plus simple à lire que les appels de méthode équivalents. Je conviens que les développeurs doivent savoir que les propriétés sont essentiellement des méthodes déguisées - mais je pense qu'il est préférable d'éduquer les développeurs à ce sujet que de rendre le code plus difficile à lire à l'aide de méthodes. (En particulier, après avoir vu Java code avec plusieurs getters et setters appelés dans la même déclaration, je sais que le code C # équivalent serait beaucoup plus simple à lire. La loi de Demeter est tout très bien en théorie, mais parfois foo.Name.Length est vraiment la bonne chose à utiliser ...)

(Et non, les propriétés implémentées automatiquement ne changent vraiment rien à cela.)

C'est un peu comme les arguments contre l'utilisation de méthodes d'extension - je peux comprendre le raisonnement, mais l'avantage pratique (lorsqu'il est utilisé avec parcimonie) l'emporte sur l'inconvénient à mon avis.

173
Jon Skeet

Eh bien, prenons ses arguments un par un:

Une propriété peut être en lecture seule ou en écriture seule; l'accès aux champs est toujours lisible et accessible en écriture.

C'est une victoire pour les propriétés, car vous avez un contrôle d'accès plus fin.

Une méthode de propriété peut lever une exception; l'accès aux champs ne lève jamais d'exception.

Bien que cela soit principalement vrai, vous pouvez très bien appeler une méthode sur un champ d'objet non initialisé et avoir une exception levée.

• Une propriété ne peut pas être transmise en tant que paramètre out ou ref à une méthode; un champ peut.

Juste.

• Une méthode de propriété peut être longue à exécuter; l'accès aux champs se termine toujours immédiatement.

Cela peut également prendre très peu de temps.

• Si elle est appelée plusieurs fois de suite, une méthode de propriété peut renvoyer une valeur différente à chaque fois; un champ renvoie la même valeur à chaque fois.

Pas vrai. Comment savez-vous que la valeur du champ n'a pas changé (éventuellement par un autre thread)?

La classe System.DateTime possède une propriété Now en lecture seule qui renvoie la date et l'heure actuelles. Chaque fois que vous interrogez cette propriété, elle renvoie une valeur différente. Il s'agit d'une erreur, et Microsoft souhaite pouvoir corriger la classe en faisant de Now une méthode au lieu d'une propriété.

S'il s'agit d'une erreur, elle est mineure.

• Une méthode des propriétés peut provoquer des effets secondaires observables; l'accès aux champs ne le fait jamais. En d'autres termes, un utilisateur d'un type doit être en mesure de définir diverses propriétés définies par un type dans l'ordre qu'il choisit sans remarquer de comportement différent dans le type.

Juste.

• Une méthode de propriété peut nécessiter de la mémoire supplémentaire ou renvoyer une référence à quelque chose qui ne fait pas réellement partie de l'état de l'objet, donc la modification de l'objet renvoyé n'a aucun effet sur l'objet d'origine; l'interrogation d'un champ renvoie toujours une référence à un objet qui est garanti de faire partie de l'état de l'objet d'origine. Travailler avec une propriété qui renvoie une copie peut être très déroutant pour les développeurs, et cette caractéristique n'est souvent pas documentée.

La plupart des protestations pouvaient également être dites pour les getters et les setters de Java - et nous les avons eu pendant un bon moment sans de tels problèmes dans la pratique.

Je pense que la plupart des problèmes pourraient être résolus par une meilleure mise en évidence de la syntaxe (c'est-à-dire en différenciant les propriétés des champs) afin que le programmeur sache à quoi s'attendre.

34
Hejazzman

Je n'ai pas lu le livre, et vous n'en avez pas cité la partie que vous ne comprenez pas, je vais donc devoir deviner.

Certaines personnes n'aiment pas les propriétés car elles peuvent faire en sorte que votre code fasse des choses surprenantes.

Si je tape Foo.Bar, les personnes qui le liront s'attendront normalement à ce qu'il accède simplement à un champ membre de la classe Foo. C'est une opération bon marché, presque gratuite et déterministe. Je peux l'appeler encore et encore et obtenir le même résultat à chaque fois.

Au lieu de cela, avec des propriétés, il pourrait en fait s'agir d'un appel de fonction. Ce pourrait être une boucle infinie. Cela pourrait ouvrir une connexion à la base de données. Il peut retourner des valeurs différentes chaque fois que j'y accède.

C'est un argument similaire pour expliquer pourquoi Linus déteste C++. Votre code peut surprendre le lecteur. Il déteste la surcharge des opérateurs: a + b ne signifie pas nécessairement un simple ajout. Cela peut signifier une opération extrêmement compliquée, tout comme les propriétés C #. Cela peut avoir des effets secondaires. Cela peut faire n'importe quoi.

Honnêtement, je pense que c'est un argument faible. Les deux langues sont pleines de choses comme ça. (Faut-il également éviter la surcharge des opérateurs en C #? Après tout, le même argument peut être utilisé ici)

Les propriétés permettent l'abstraction. Nous pouvons faire semblant que quelque chose est un champ régulier, et l'utiliser comme s'il en était un, et ne pas avoir à nous soucier de ce qui se passe dans les coulisses.

C'est généralement considéré comme une bonne chose, mais cela dépend évidemment du programmeur qui écrit des abstractions significatives. Vos propriétés devraient se comporter comme des champs. Ils ne devraient pas avoir d'effets secondaires, ils ne devraient pas effectuer d'opérations coûteuses ou dangereuses. Nous voulons pouvoir pensez à eux comme des champs.

Cependant, j'ai une autre raison de les trouver moins que parfaits. Ils ne peuvent pas être transmis par référence à d'autres fonctions.

Les champs peuvent être passés comme ref, permettant à une fonction appelée d'y accéder directement. Les fonctions peuvent être passées en tant que délégués, permettant à une fonction appelée d'y accéder directement.

Les propriétés ... ne peuvent pas.

Ça craint.

Mais cela ne signifie pas que les propriétés sont mauvaises ou ne devraient pas être utilisées. À de nombreuses fins, ils sont excellents.

18
jalf

En 2009, ce conseil ressemblait simplement à de la variété Who Moved My Cheese . Aujourd'hui, c'est presque ridiculement obsolète.

Un point très important que de nombreuses réponses semblent sur la pointe des pieds, mais ne traitent pas de front, c'est que ces prétendus "dangers" des propriétés sont une partie intentionnelle de la conception du cadre!

Oui, les propriétés peuvent:

  • Spécifiez différents modificateurs d'accès pour le getter et le setter. C'est un avantage sur les champs. Un modèle courant est d'avoir un getter public et un setter protégé ou interne, une technique d'héritage très utile qui n'est pas réalisable par les seuls champs.

  • Jetez une exception. À ce jour, cela reste l'une des méthodes de validation les plus efficaces, en particulier lorsque vous travaillez avec des cadres d'interface utilisateur qui impliquent des concepts de liaison de données. Il est beaucoup plus difficile de garantir qu'un objet reste dans un état valide lorsque vous travaillez avec des champs.

  • Prenez beaucoup de temps à exécuter. La comparaison valide ici est avec méthodes, qui prennent également longtemps - pas champs. Aucune base n'est donnée pour l'énoncé "une méthode est préférée" autre que la préférence personnelle d'un auteur.

  • Renvoie différentes valeurs de son getter lors des exécutions suivantes. Cela ressemble presque à une blague si proche du point vantant les vertus des paramètres ref/out avec des champs, dont la valeur d'un champ après un ref/out est à peu près garanti différent de sa valeur précédente, et de manière imprévisible.

    Si nous parlons du cas spécifique (et pratiquement académique) de l'accès à un seul thread sans couplage afférent, c'est assez bien compris que c'est juste une mauvaise conception de propriété d'avoir un côté changeant d'état visible- effets, et peut-être que ma mémoire s'estompe, mais je n'arrive pas à me souvenir d'exemples de personnes utilisant DateTime.Now et s'attendant à ce que la même valeur sorte à chaque fois. Du moins, pas d'exemples où ils n'auraient pas tout gâché avec une hypothétique DateTime.Now().

  • Provoquer des effets secondaires observables - ce qui est bien sûr précisément la raison pour laquelle les propriétés ont été inventées en tant que caractéristique du langage en premier lieu. Les propres directives de Microsoft Property Design indiquent que l'ordre de définition ne devrait pas avoir d'importance, car sinon, cela impliquerait couplage temporel . Certes, vous ne pouvez pas réaliser de couplage temporel avec des champs seuls, mais c'est uniquement parce que vous ne pouvez pas provoquer de comportement significatif avec des champs seuls, jusqu'à ce qu'une méthode soit exécutée.

    Les accesseurs de propriété peuvent réellement aider à éviter certains types de couplage temporel en forçant l'objet dans un état valide avant toute action - par exemple, si une classe a un StartDate et un EndDate, puis en définissant EndDate avant que StartDate puisse également forcer StartDate. Cela est vrai même dans des environnements multithreads ou asynchrones, y compris l'exemple évident d'une interface utilisateur événementielle.

D'autres choses que les propriétés peuvent faire et que les champs ne peuvent pas inclure:

  • chargement paresseux , l'un des moyens les plus efficaces de prévenir les erreurs d'ordre d'initialisation.
  • Changer les notifications , qui sont à peu près la base entière de l'architecture MVVM .
  • Héritage , par exemple en définissant un résumé Type ou Name afin que les classes dérivées puissent fournir des métadonnées intéressantes mais néanmoins constantes sur elles-mêmes.
  • Interception , grâce à ce qui précède.
  • Indexeurs , que tous ceux qui ont déjà eu à travailler avec COM interop et le crachement inévitable des appels de Item(i) reconnaîtront comme une chose merveilleuse.
  • Travaillez avec PropertyDescriptor qui est essentiel pour créer des concepteurs et pour les frameworks XAML en général.

Richter est clairement un auteur prolifique et en sait beaucoup sur le CLR et le C #, mais je dois dire, il semble que quand il a écrit à l'origine ce conseil (je ne sais pas si c'est dans ses révisions les plus récentes - j'espère sincèrement que non) qu'il ne voulait tout simplement pas abandonner ses vieilles habitudes et avait du mal à accepter les conventions de C # (vs C++, par exemple).

Ce que je veux dire par là, c'est que son argument "propriétés considérées comme nuisibles" se résume essentiellement à une seule déclaration: Les propriétés ressemblent à des champs, mais elles pourraient ne pas agir comme des champs. Et le problème avec la déclaration est, ce n'est pas vrai , ou au mieux c'est très trompeur. Les propriétés pas ressemblent à des champs - au moins, elles ne sont pas supposées à ressembler à des champs.

Il existe deux très conventions de codage fortes en C # avec des conventions similaires partagées par d'autres langages CLR, et FXCop vous hurlera si vous ne les suivez pas:

  1. Les champs doivent toujours être privés, jamais publics.
  2. Les champs doivent être déclarés dans camelCase. Les propriétés sont PascalCase.

Ainsi, il n'y a aucune ambiguïté quant à savoir si Foo.Bar = 42 Est un accesseur de propriété ou un accesseur de champ. C'est un accesseur de propriété et doit être traité comme n'importe quelle autre méthode - il peut être lent, il peut lever une exception, etc. C'est la nature de Abstraction - c'est entièrement à la discrétion de la classe déclarante comment réagir. Les concepteurs de classe devraient appliquer le principe de la moindre surprise, mais les appelants ne devraient rien supposer d'une propriété, sauf qu'elle fait ce qu'elle dit sur l'étain. C'est exprès.

L'alternative aux propriétés est les méthodes getter/setter partout. C'est l'approche Java, et ça a été controversé depuis le début . C'est bien si c'est votre sac, mais ce n'est pas comme ça que nous roulons dans le camp .NET. Nous essayez, au moins dans les limites d'un système de type statique, d'éviter ce que Fowler appelle Bruit syntaxique . Nous ne voulons pas de parenthèses supplémentaires, extra get/set verrues ou signatures de méthode supplémentaires - pas si nous pouvons les éviter sans aucune perte de clarté.

Dites ce que vous voulez, mais foo.Bar.Baz = quux.Answers[42] Sera toujours beaucoup plus facile à lire que foo.getBar().setBaz(quux.getAnswers().getItem(42)). Et lorsque vous lisez des milliers de lignes par jour, cela fait une différence.

(Et si votre réponse naturelle au paragraphe ci-dessus est de dire "bien sûr, c'est difficile à lire, mais ce serait plus facile si vous le divisez en plusieurs lignes", alors je suis désolé de dire que vous avez complètement a raté le point.)

17
Aaronaught

Je ne vois aucune raison pour laquelle vous ne devriez pas utiliser les propriétés en général.

Les propriétés automatiques en C # 3+ simplifient un peu la syntaxe (à la manière du sucre syntaxique).

11
Konstantin Tarkus

Ce n'est que l'opinion d'une seule personne. J'ai lu pas mal de livres c # et je n'ai encore vu personne dire "n'utilisez pas de propriétés".

Personnellement, je pense que les propriétés sont l'une des meilleures choses à propos de c #. Ils vous permettent d'exposer l'état via le mécanisme que vous souhaitez. Vous pouvez paresseusement instancier la première fois que quelque chose est utilisé et vous pouvez faire une validation sur la définition d'une valeur, etc.

Quant aux mises en garde avec des propriétés, il y en a deux. L'une est probablement une mauvaise utilisation des propriétés, l'autre peut être subtile.

Premièrement, les propriétés sont des types de méthodes. Il peut être surprenant de placer une logique compliquée dans une propriété, car la plupart des utilisateurs d'une classe s'attendent à ce que la propriété soit assez légère.

Par exemple.

public class WorkerUsingMethod
{
   // Explicitly obvious that calculation is being done here
   public int CalculateResult()
   { 
      return ExpensiveLongRunningCalculation();
   }
}

public class WorkerUsingProperty
{
   // Not at all obvious.  Looks like it may just be returning a cached result.
   public int Result
   {
       get { return ExpensiveLongRunningCalculation(); }
   }
}

Je trouve que l'utilisation de méthodes pour ces cas permet de faire une distinction.

Deuxièmement, et plus important encore, les propriétés peuvent avoir des effets secondaires si vous les évaluez pendant le débogage.

Disons que vous avez une propriété comme celle-ci:

public int Result 
{ 
   get 
   { 
       m_numberQueries++; 
       return m_result; 
   } 
}

Supposons maintenant que vous ayez une exception qui se produit lorsque trop de requêtes sont effectuées. Devinez ce qui se passe lorsque vous démarrez le débogage et survolez la propriété dans le débogueur? Mauvaises choses. Évitez de faire ça! La consultation de la propriété modifie l'état du programme.

Ce sont les seules mises en garde que j'ai. Je pense que les avantages des propriétés l'emportent largement sur les problèmes.

9
Mark Simpson

Je ne peux m'empêcher de choisir les détails des opinions de Jeffrey Richter:

Une propriété peut être en lecture seule ou en écriture seule; l'accès aux champs est toujours lisible et accessible en écriture.

Mauvais: les champs peuvent être marqués en lecture seule afin que seul le constructeur de l'objet puisse y écrire.

Une méthode de propriété peut lever une exception; l'accès aux champs ne lève jamais d'exception.

Incorrect: l'implémentation d'une classe peut faire passer le modificateur d'accès d'un champ de public à privé. Les tentatives de lecture de champs privés lors de l'exécution entraîneront toujours une exception.

6
Cecil Has a Name

Cette raison doit avoir été donnée dans un contexte très précis. C'est généralement l'inverse - il est recommandé d'utiliser des propriétés car elles vous donnent un niveau d'abstraction vous permettant de changer le comportement d'une classe sans affecter ses clients ...

6
Rashack

Je ne suis pas d'accord avec Jeffrey Richter, mais je peux deviner pourquoi il n'aime pas les propriétés (je n'ai pas lu son livre).

Même si les propriétés sont exactement comme les méthodes (au niveau de l'implémentation), en tant qu'utilisateur d'une classe, je m'attends à ce que ses propriétés se comportent "plus ou moins" comme un champ public, par exemple:

  • il n'y a pas d'opération fastidieuse à l'intérieur de la propriété getter/setter
  • le getter de propriété n'a aucun effet secondaire (l'appeler plusieurs fois, ne change pas le résultat)

Malheureusement, j'ai vu des propriétés qui ne se comportaient pas de cette façon. Mais le problème n'est pas les propriétés elles-mêmes, mais les personnes qui les ont mises en œuvre. Il faut donc juste un peu d'éducation.

5
M4N

Vous ne devez pas éviter de les utiliser mais vous devez les utiliser avec qualification et soin, pour les raisons données par d'autres contributeurs.

Une fois, j'ai vu une propriété appelée quelque chose comme Clients qui a ouvert en interne un appel hors processus vers une base de données et lu la liste des clients. Le code client avait un "for (int i to Customers.Count)" qui provoquait un appel distinct à la base de données à chaque itération et pour l'accès du client sélectionné. C'est un exemple flagrant qui démontre le principe de garder la propriété très légère - rarement plus qu'un accès interne au champ.

Un argument POUR l'utilisation des propriétés est qu'elles vous permettent de valider la valeur définie. Un autre est que la valeur de la propriété peut être une valeur dérivée, pas un seul champ, comme TotalValue = montant * quantité.

4
Rob Kent

Il y a un moment où j'envisage de ne pas utiliser de propriétés, et c'est en écrivant du code .Net Compact Framework. Le compilateur CF JIT n'effectue pas la même optimisation que le compilateur JIT de bureau et n'optimise pas les accesseurs de propriétés simples, donc dans ce cas, l'ajout d'une propriété simple provoque une légère surcharge de code lors de l'utilisation d'un champ public. Habituellement, ce ne serait pas un problème, mais presque toujours dans le monde du Compact Framework, vous êtes confronté à des contraintes de mémoire strictes, donc même de petites économies comme celle-ci comptent.

4
Steve

Personnellement, j'utilise uniquement des propriétés lors de la création de méthodes get/set simples. Je m'en éloigne quand j'arrive à des structures de données compliquées.

3
Steve

L'argument suppose que les propriétés sont mauvaises car elles ressemblent à des champs, mais peuvent effectuer des actions surprenantes. Cette hypothèse est invalidée par les attentes des programmeurs .NET:

Les propriétés ne ressemblent pas à des champs. Les champs ressemblent à des propriétés.

• Une méthode de propriété peut lever une exception; l'accès aux champs ne lève jamais d'exception.

Ainsi, un champ est comme une propriété qui est garantie de ne jamais lever d'exception.

• Une propriété ne peut pas être transmise en tant que paramètre out ou ref à une méthode; un champ peut.

Ainsi, un champ est comme une propriété, mais il a des capacités supplémentaires: passer à une méthode d'acceptation ref/out.

• Une méthode de propriété peut être longue à exécuter; l'accès aux champs se termine toujours immédiatement. [...]

Ainsi, un champ est comme une propriété rapide.

• Si elle est appelée plusieurs fois de suite, une méthode de propriété peut renvoyer une valeur différente à chaque fois; un champ renvoie la même valeur à chaque fois. La classe System.DateTime possède une propriété Now en lecture seule qui renvoie la date et l'heure actuelles.

Ainsi, un champ est comme une propriété qui est garantie de renvoyer la même valeur, sauf si le champ a été défini sur une valeur différente.

• Une méthode des propriétés peut provoquer des effets secondaires observables; l'accès aux champs ne le fait jamais.

Encore une fois, un champ est une propriété qui ne garantit pas cela.

• Une méthode de propriété peut nécessiter de la mémoire supplémentaire ou renvoyer une référence à quelque chose qui ne fait pas réellement partie de l'état de l'objet, donc la modification de l'objet renvoyé n'a aucun effet sur l'objet d'origine; l'interrogation d'un champ renvoie toujours une référence à un objet qui est garanti de faire partie de l'état de l'objet d'origine. Travailler avec une propriété qui renvoie une copie peut être très déroutant pour les développeurs, et cette caractéristique n'est souvent pas documentée.

Celui-ci peut être surprenant, mais pas parce que c'est une propriété qui fait cela. C'est plutôt que presque personne ne retourne des copies mutables dans les propriétés, de sorte qu'un cas de 0,1% est surprenant.

3
milleniumbug

L'appel de méthodes au lieu de propriétés réduit considérablement la lisibilité du code d'appel. En J #, par exemple, utiliser ADO.NET était un cauchemar car Java ne prend pas en charge les propriétés et les indexeurs (qui sont essentiellement des propriétés avec des arguments). Le code résultant était extrêmement moche, avec des parenthèses vides la méthode appelle partout.

La prise en charge des propriétés et des indexeurs est l'un des avantages de base de C # par rapport à Java.

0
Marty Solomon