En C # (et dans de nombreux autres langages), il est parfaitement légitime d'accéder aux champs privés d'autres instances du même type. Par exemple:
public class Foo
{
private bool aBool;
public void DoBar(Foo anotherFoo)
{
if (anotherFoo.aBool) ...
}
}
Comme l'indique la spécification C # (sections 3.5.1, 3.5.2), l'accès aux champs privés se fait sur un type et non sur une instance. J'en ai discuté avec un collègue et nous essayons de trouver une raison pour laquelle cela fonctionne comme ça (plutôt que de restreindre l'accès à la même instance).
Le meilleur argument que nous pourrions trouver est pour les vérifications d'égalité où la classe peut vouloir accéder aux champs privés pour déterminer l'égalité avec une autre instance. Y a-t-il d'autres raisons? Ou une raison en or qui signifie absolument que cela doit fonctionner comme ça ou quelque chose serait complètement impossible?
Réponses:
Je pense qu'une des raisons pour lesquelles cela fonctionne de cette façon est que les modificateurs d'accès fonctionnent au moment de la compilation . En tant que tel, il n'est pas facile de déterminer si un objet donné est également l' objet actuel . Par exemple, considérez ce code:
Le compilateur peut-il nécessairement comprendre que
other
c'est réellementthis
? Pas dans tous les cas. On pourrait dire que cela ne devrait tout simplement pas être compilé à ce moment-là, mais cela signifie que nous avons un chemin de code dans lequel un membre d'instance privée de l'instance correcte n'est pas accessible, ce qui est encore pire à mon avis.Le seul fait d'exiger une visibilité au niveau du type plutôt qu'au niveau de l'objet garantit que le problème est résolu, ainsi qu'une situation qui semble devoir fonctionner réellement .
EDIT : Le point de Danilel Hilgarth selon lequel ce raisonnement est à l'envers a du mérite. Les concepteurs de langage peuvent créer le langage de leur choix et les rédacteurs de compilateurs doivent s'y conformer. Cela étant dit, les concepteurs de langage sont incités à faciliter la tâche des rédacteurs de compilateurs dans leur travail. (Bien que dans ce cas, il est assez facile de faire valoir que les simples députés pourraient alors ne sont accessibles via
this
(implicitement ou explicitement)).Cependant, je pense que cela rend la question plus déroutante qu'elle ne devrait l'être. La plupart des utilisateurs (moi y compris) trouveraient inutilement limitant si le code ci-dessus ne fonctionnait pas: après tout, ce sont mes données auxquelles j'essaie d'accéder! Pourquoi devrais-je traverser
this
?En bref, je pense que j'ai peut-être surestimé le fait que ce soit "difficile" pour le compilateur. Ce que je voulais vraiment faire comprendre, c'est que la situation ci-dessus semble être celle que les concepteurs aimeraient avoir du travail.
la source
this.bar = 2
mais nonother.bar = 2
, car ilother
pourrait s'agir d'une instance différente.Parce que le but du type d'encapsulation utilisé en C # et dans les langages similaires * est de réduire la dépendance mutuelle de différents morceaux de code (classes en C # et Java), et non d'objets différents en mémoire.
Par exemple, si vous écrivez du code dans une classe qui utilise certains champs dans une autre classe, ces classes sont très étroitement couplées. Cependant, si vous utilisez du code dans lequel vous avez deux objets de la même classe, il n'y a pas de dépendance supplémentaire. Une classe dépend toujours d'elle-même.
Cependant, toute cette théorie sur l'encapsulation échoue dès que quelqu'un crée des propriétés (ou des paires get / set en Java) et expose directement tous les champs, ce qui rend les classes aussi couplées comme si elles accédaient aux champs de toute façon.
* Pour des éclaircissements sur les types d'encapsulation, voir l'excellente réponse d'Abel.
la source
get/set
méthodes sont fournies. Les méthodes d'accès conservent toujours une abstraction (l'utilisateur n'a pas besoin de savoir qu'il y a un champ derrière lui, ou quel (s) champ (s) peuvent être derrière la propriété). Par exemple, si nous écrivons uneComplex
classe, nous pouvons exposer des propriétés pour obtenir / définir les coordonnées polaires, et un autre get / set pour les coordonnées cartésiennes. Lequel la classe utilise en dessous est inconnu de l'utilisateur (cela pourrait même être quelque chose de complètement différent).private
. Vous pouvez vérifier ma réponse pour mon avis sur les choses si vous le souhaitez;).De nombreuses réponses ont déjà été ajoutées à ce sujet intéressant, cependant, je n'ai pas tout à fait trouvé la vraie raison pour laquelle ce comportement est tel qu'il est. Laissez-moi essayer:
De mon temps
Quelque part entre Smalltalk dans les années 80 et Java au milieu des années 90, le concept d'orientation objet a mûri. Le masquage d'informations, qui n'était pas initialement considéré comme un concept uniquement disponible pour OO (mentionné pour la première fois en 1978), a été introduit dans Smalltalk car toutes les données (champs) d'une classe sont privées, toutes les méthodes sont publiques. Au cours des nombreux nouveaux développements d'OO dans les années 90, Bertrand Meyer a tenté de formaliser une grande partie des concepts OO dans son livre historique Object Oriented Software Construction (OOSC). qui a depuis lors été considéré comme une référence (presque) définitive sur les concepts OO et la conception du langage. .
En cas de visibilité privée
Selon Meyer, une méthode devrait être mise à la disposition d'un ensemble défini de classes (page 192-193). Cela donne évidemment une très grande granularité de masquage des informations, la fonctionnalité suivante est disponible pour classA et classB et tous leurs descendants:
Dans le cas où
private
il dit ce qui suit: sans déclarer explicitement un type comme visible pour sa propre classe, vous ne pouvez pas accéder à cette fonctionnalité (méthode / champ) dans un appel qualifié. Ie six
est une variable,x.doSomething()
n'est pas autorisé. L'accès non qualifié est bien entendu autorisé à l'intérieur de la classe elle-même.En d'autres termes: pour autoriser l'accès par une instance de la même classe, vous devez autoriser l'accès à la méthode par cette classe explicitement. Ceci est parfois appelé instance-privé ou classe-privé.
Instance privée dans les langages de programmation
Je connais au moins deux langues actuellement utilisées qui utilisent le masquage d'informations privées d'instance par opposition au masquage d'informations privées de classe. L'un est Eiffel, un langage conçu par Meyer, qui pousse OO à ses extrêmes. L'autre étant Ruby, une langue beaucoup plus courante de nos jours. En Ruby,
private
signifie: "privé pour cette instance" .Choix pour la conception du langage
Il a été suggéré qu'autoriser l'instance privée serait difficile pour le compilateur. Je ne pense pas, car il est relativement simple d'autoriser ou d'interdire les appels qualifiés aux méthodes. Si pour une méthode privée,
doSomething()
est autorisé etx.doSomething()
ne l'est pas, un concepteur de langage a effectivement défini l'accessibilité d'instance uniquement pour les méthodes et les champs privés.D'un point de vue technique, il n'y a aucune raison de choisir l'une ou l'autre manière (surtout si l'on considère qu'Eiffel.NET peut faire cela avec IL, même avec l'héritage multiple, il n'y a aucune raison inhérente de ne pas fournir cette fonctionnalité).
Bien sûr, c'est une question de goût et comme d'autres l'ont déjà mentionné, certaines méthodes pourraient être plus difficiles à écrire sans la fonctionnalité de visibilité au niveau de la classe des méthodes et des champs privés.
Pourquoi C # n'autorise que l'encapsulation de classe et non l'encapsulation d'instance
Si vous regardez les fils Internet sur l'encapsulation d'instances (un terme parfois utilisé pour désigner le fait qu'un langage définit les modificateurs d'accès au niveau de l'instance, par opposition au niveau de la classe), le concept est souvent mal vu. Cependant, étant donné que certains langages modernes utilisent l'encapsulation d'instance, au moins pour le modificateur d'accès privé, vous pensez qu'il peut être et est utile dans le monde de la programmation moderne.
Cependant, C # a certainement regardé le plus dur C ++ et Java pour sa conception de langage. Alors qu'Eiffel et Modula-3 étaient également dans l'image, compte tenu des nombreuses fonctionnalités d'Eiffel manquantes (héritage multiple), je pense qu'ils ont choisi le même chemin que Java et C ++ en ce qui concerne le modificateur d'accès privé.
Si vous voulez vraiment savoir pourquoi vous devriez essayer de mettre la main sur Eric Lippert, Krzysztof Cwalina, Anders Hejlsberg ou toute autre personne qui a travaillé sur le standard de C #. Malheureusement, je n'ai pas trouvé de note définitive dans le langage de programmation annoté C # .
la source
Foo
représentait effectivement une interface et une classe d'implémentation; comme COM n'avait pas de concept de référence de classe-objet - juste une référence d'interface - il n'y avait aucun moyen pour une classe de contenir une référence à quelque chose qui était garanti comme une autre instance de cette classe. Cette conception basée sur une interface rendait certaines choses fastidieuses, mais cela signifiait que l'on pouvait concevoir une classe qui était substituable à une autre sans avoir à partager les mêmes éléments internes.Ce n'est que mon avis, mais de manière pragmatique, je pense que si un programmeur a accès à la source d'une classe, vous pouvez raisonnablement lui faire confiance pour accéder aux membres privés de l'instance de classe. Pourquoi lier un programmeur à la main droite alors que dans sa gauche vous leur avez déjà donné les clés du royaume?
la source
La raison en est en effet le contrôle d'égalité, la comparaison, le clonage, la surcharge d'opérateurs ... Il serait très délicat d'implémenter operator + sur des nombres complexes, par exemple.
la source
readonly
et cela s'applique également à l'instance déclarante. De toute évidence, vous affirmez qu'il devrait y avoir une règle de compilation spécifique pour l'interdire, mais chaque règle de ce type est une fonctionnalité que l'équipe C # doit spécifier, documenter, localiser, tester, maintenir et prendre en charge. S'il n'y a pas d' avantage convaincant, pourquoi le feraient-ils?Tout d'abord, qu'arriverait-il aux membres statiques privés? Peuvent-ils être accessibles uniquement par des méthodes statiques? Vous ne voudriez certainement pas cela, car vous ne seriez alors pas en mesure d'accéder à votre
const
s.En ce qui concerne votre question explicite, considérons le cas de a
StringBuilder
, qui est implémenté comme une liste chaînée d'instances d'elle-même:Si vous ne pouvez pas accéder aux membres privés d'autres instances de votre propre classe, vous devez implémenter
ToString
comme ceci:Cela fonctionnera, mais c'est O (n ^ 2) - pas très efficace. En fait, cela va probablement à l'encontre de l'objectif général d'avoir une
StringBuilder
classe en premier lieu. Si vous pouvez accéder aux membres privés d'autres instances de votre propre classe, vous pouvez implémenterToString
en créant une chaîne de la longueur appropriée, puis en faisant une copie non sécurisée de chaque morceau à son emplacement approprié dans la chaîne:Cette implémentation est O (n), ce qui la rend très rapide, et n'est possible que si vous avez accès aux membres privés d'autres instances de votre classe .
la source
internal
rend moins privé! Vous finiriez par exposer les éléments internes de votre classe à tout ce qui se trouve dans son assemblage.Ceci est parfaitement légitime dans de nombreux langages (C ++ pour un). Les modificateurs d'accès proviennent du principe d'encapsulation en POO. L'idée est de restreindre l'accès à l' extérieur , dans ce cas à l'extérieur se trouvent d'autres classes. Toute classe imbriquée en C # par exemple peut également accéder aux membres privés de ses parents.
Bien qu'il s'agisse d'un choix de conception pour un concepteur de langage. La restriction de cet accès peut compliquer certains scénarios très courants sans trop contribuer à l'isolation des entités.
Il y a une discussion similaire ici
la source
Je ne pense pas qu'il y ait une raison pour laquelle nous ne pourrions pas ajouter un autre niveau de confidentialité, où les données sont privées pour chaque instance. En fait, cela pourrait même donner une belle sensation d'exhaustivité à la langue.
Mais en pratique, je doute que ce soit vraiment utile. Comme vous l'avez souligné, notre caractère privé habituel est utile pour des choses telles que les vérifications d'égalité, ainsi que pour la plupart des autres opérations impliquant plusieurs instances d'un type. Bien que j'apprécie également votre point sur le maintien de l'abstraction des données, car c'est un point important dans la POO.
Je pense que tout autour, fournir la possibilité de restreindre l'accès de cette manière pourrait être une fonctionnalité intéressante à ajouter à la POO. Est-ce vraiment utile? Je dirais non, car une classe devrait pouvoir faire confiance à son propre code. Étant donné que cette classe est la seule chose qui peut accéder aux membres privés, il n'y a aucune raison réelle d'avoir besoin d'abstraction de données lorsqu'il s'agit d'une instance d'une autre classe.
Bien sûr, vous pouvez toujours écrire votre code comme s'il était privé appliqué aux instances. Utilisez les
get/set
méthodes habituelles pour accéder / modifier les données. Cela rendrait probablement le code plus gérable si la classe pouvait être soumise à des modifications internes.la source
Excellentes réponses données ci-dessus. J'ajouterais qu'une partie de ce problème est le fait que l'instanciation d'une classe à l'intérieur d'elle-même est même autorisée en premier lieu. Cela permet depuis dans une logique récursive de boucle "for", par exemple, d'utiliser ce type d'astuce tant que vous avez une logique pour terminer la récursivité. Mais instancier ou passer la même classe à l'intérieur d'elle-même sans créer de telles boucles crée logiquement ses propres dangers, même si c'est un paradigme de programmation largement accepté. Par exemple, une classe C # peut instancier une copie d'elle-même dans son constructeur par défaut, mais cela n'enfreint aucune règle ni ne crée de boucles causales. Pourquoi?
BTW .... ce même problème s'applique également aux membres «protégés». :(
Je n'ai jamais accepté pleinement ce paradigme de programmation, car il comporte encore toute une série de problèmes et de risques que la plupart des programmeurs ne saisissent pas complètement tant que des problèmes comme ce problème ne surviennent pas et déroutent les gens et défient toute la raison d'avoir des membres privés.
Cet aspect "bizarre et farfelu" de C # est encore une raison de plus pour laquelle une bonne programmation n'a rien à voir avec l'expérience et les compétences, mais juste connaître les trucs et les pièges ..... comme travailler sur une voiture. C'est l'argument selon lequel les règles étaient censées être enfreintes, ce qui est un très mauvais modèle pour tout langage informatique.
la source
Il me semble que si les données étaient privées pour d'autres instances du même type, elles ne seraient plus nécessairement du même type. Il ne semble pas se comporter ou agir de la même manière que les autres instances. Le comportement pourrait facilement être modifié en fonction de ces données internes privées. Cela ne ferait que semer la confusion à mon avis.
En gros, je pense personnellement que l'écriture de classes dérivées d'une classe de base offre des fonctionnalités similaires que vous décrivez avec «avoir des données privées par instance». Au lieu de cela, vous avez juste une nouvelle définition de classe par type «unique».
la source