Lors de la programmation en C #, je suis tombé sur une décision de conception de langage étrange que je ne comprenais tout simplement pas.
Donc, C # (et le CLR) a deux types de données d'agrégat: struct
(type-valeur, stocké sur la pile, pas d'héritage) et class
(type-référence, stocké sur le tas, a l'héritage).
Cette configuration sonne bien au début, mais vous tombez ensuite sur une méthode prenant un paramètre de type agrégé. Pour déterminer s’il s’agit bien d’un type de valeur ou d’un type de référence, vous devez trouver la déclaration de ce type. Cela peut parfois être très déroutant.
La solution généralement acceptée au problème semble être de déclarer tous les struct
s comme "immuables" (en définissant leurs champs sur readonly
) pour éviter les erreurs possibles, ce qui limite struct
leur utilité.
C ++, par exemple, utilise un modèle beaucoup plus utilisable: il vous permet de créer une instance d'objet sur la pile ou sur le tas et de la transmettre par valeur ou par référence (ou par pointeur). J'entends toujours que C # a été inspiré par C ++ et je ne comprends pas pourquoi il n'a pas utilisé cette technique. La combinaison class
et struct
dans une construction avec deux options d'allocation différentes (tas et la pile) et de les transmettre autour de valeurs ou (explicitement) comme références via ref
et out
mots - clés semble comme une chose agréable.
La question qui se pose est la suivante: pourquoi les concepts séparés en C # et le CLR sont class
-ils struct
devenus des concepts distincts au lieu d’un type agrégé avec deux options d’allocation?
la source
struct
s ne sont pas toujours stockés sur la pile; considérons un objet avec unstruct
champ. Cela dit, comme l'a mentionné Mason Wheeler, le problème de découpage en tranches est probablement la principale raison.Réponses:
La raison pour laquelle C # (et Java et pratiquement tous les autres langages OO développés après C ++) n'a pas copié le modèle C ++ dans cet aspect est parce que la façon dont C ++ le fait est un désordre épouvantable.
Vous avez correctement identifié les points pertinents ci-dessus:
struct
type de valeur, pas d'héritage.class
: type de référence, a héritage. Les types d'héritage et de valeur (ou plus spécifiquement, le polymorphisme et la valeur par valeur) ne font pas bon ménage. Si vous passez un objet de typeDerived
à un argument de méthodeBase
et que vous appelez ensuite une méthode virtuelle, le seul moyen d'obtenir un comportement correct est de s'assurer que ce qui est passé est une référence.Entre cela et tous les autres dégâts que vous rencontrez en C ++ en ayant des objets pouvant être hérités en tant que types de valeur (les constructeurs de copie et le découpage d’objets vous viennent à l’esprit!), La meilleure solution est de Just Say No.
Une bonne conception linguistique ne consiste pas seulement à implémenter des fonctionnalités, mais également à identifier les fonctionnalités à ne pas implémenter. L'un des meilleurs moyens de le faire est d'apprendre des erreurs commises par ceux qui vous ont précédé.
la source
Par analogie, C # est fondamentalement comme un ensemble d'outils de mécanicien dans lequel quelqu'un a lu qu'il fallait généralement éviter les pinces et les clés à molette, de sorte qu'il n'inclut pas de clés à molette, et que les pinces sont verrouillées dans un tiroir spécial portant la mention "non sécurisé". , et ne peut être utilisé qu'avec l'approbation d'un superviseur, après avoir signé un désistement dégageant votre employeur de toute responsabilité vis-à-vis de votre santé.
C ++, en comparaison, comprend non seulement des clés et des pinces ajustables, mais également des outils spéciaux plutôt bizarres dont le but n'est pas immédiatement évident. Si vous ne connaissez pas la bonne façon de les tenir, ils risquent de vous couper facilement la main. thumb (mais une fois que vous avez compris comment les utiliser, vous pouvez faire des choses qui sont essentiellement impossibles avec les outils de base de la boîte à outils C #). En outre, il dispose d'un tour, d'une fraiseuse, d'une rectifieuse, d'une scie à ruban à métaux, etc., pour vous permettre de concevoir et de créer de tout nouveaux outils chaque fois que vous en ressentez le besoin (mais, oui, les outils de ces machinistes peuvent et vont causer blessures graves si vous ne savez pas ce que vous faites avec eux - ou même si vous ne faites pas attention à rien).
Cela reflète la différence fondamentale de philosophie: le C ++ tente de vous fournir tous les outils dont vous pourriez avoir besoin pour la conception de votre choix. Il ne fait pratiquement aucun effort pour contrôler la manière dont vous utilisez ces outils. Il est donc également facile de les utiliser pour créer des dessins qui ne fonctionnent bien que dans de rares situations, ainsi que des dessins qui ne sont probablement qu'une idée moche et que personne ne connaît. ils sont susceptibles de bien fonctionner. En particulier, une grande partie de cela est réalisée en découplant les décisions de conception - même celles qui, dans la pratique, sont presque toujours couplées. En conséquence, il y a une énorme différence entre juste écrire en C ++ et bien écrire en C ++. Pour bien écrire en C ++, vous devez connaître de nombreux idiomes et règles empiriques (y compris les règles empiriques expliquant comment reconsidérer sérieusement la situation avant d'enfreindre d'autres règles empiriques). Par conséquent, C ++ est beaucoup plus orienté vers la facilité d'utilisation (par les experts) que la facilité d'apprentissage. Il y a aussi (beaucoup trop) de cas dans lesquels il n'est pas vraiment facile à utiliser non plus.
C # fait beaucoup plus pour essayer de forcer (ou du moins très fortement suggérer) ce que les concepteurs de langage ont considéré comme de bonnes pratiques de conception. Un certain nombre de choses qui sont découplées en C ++ (mais vont généralement ensemble) sont directement couplées en C #. Cela permet au code "non sécurisé" de repousser légèrement les limites, mais honnêtement, pas beaucoup.
Il en résulte qu’un certain nombre de conceptions pouvant être exprimées assez directement en C ++ sont plus difficiles à exprimer en C #. D'autre part, il est un ensemble beaucoup plus facile à apprendre C #, et les chances de produire un design vraiment horrible qui ne fonctionnera pas pour votre situation (ou probablement tout autre) sont considérablement réduits. Dans de nombreux cas (probablement même dans la plupart des cas), vous pouvez obtenir une conception solide et réalisable simplement en "allant avec le courant", pour ainsi dire. Ou, comme l'un de mes amis (du moins j'aime le penser comme un ami - je ne sais pas s'il est vraiment d'accord) aime le dire, C # facilite la chute dans le gouffre du succès.
Donc, en regardant plus spécifiquement la question de savoir comment
class
etstruct
comment ils sont dans les deux langages: objets créés dans une hiérarchie d'héritage où vous pourriez utiliser un objet d'une classe dérivée sous le couvert de sa classe / interface de base, vous êtes à peu près coincé avec le fait que vous devez normalement le faire via une sorte de pointeur ou de référence - à un niveau concret, ce qui se passe est que l'objet de la classe dérivée contient quelque chose de mémoire qui peut être traitée comme une instance de la classe de base / l'interface, et l'objet dérivé est manipulé via l'adresse de cette partie de la mémoire.En C ++, il appartient au programmeur de le faire correctement - lorsqu'il utilise l'héritage, il lui appartient de s'assurer que, par exemple, une fonction qui fonctionne avec des classes polymorphes dans une hiérarchie le fait via un pointeur ou une référence à la base. classe.
En C #, ce qui est fondamentalement la même séparation entre les types est beaucoup plus explicite et imposé par le langage lui-même. Le programmeur n'a pas besoin de passer à l'étape suivante pour passer une instance d'une classe par référence, car cela se produira par défaut.
la source
Cela vient de "C #: Pourquoi avons-nous besoin d'une autre langue?" - Gunnerson, Eric:
La sémantique de référence pour les objets est un moyen d'éviter beaucoup de problèmes (bien sûr et pas seulement le découpage d'objet), mais les problèmes du monde réel peuvent parfois nécessiter des objets avec une sémantique de valeur (par exemple, regardez Sons comme je ne devrais jamais utiliser de sémantique de référence, non? pour un point de vue différent).
Quelle meilleure approche à adopter, donc, que de séparer ces objets sales, laids et mauvais avec une valeur sémantique sous la balise de
struct
?la source
int[]
devait être partageable ou modifiable (les tableaux pouvaient être l'un ou l'autre, mais généralement pas les deux) aiderait à donner un faux code à un code erroné.Plutôt que de penser aux types de valeur dérivés
Object
, il serait plus utile de penser aux types d'emplacement de stockage existant dans un univers entièrement distinct des types d'instance de classe, mais que chaque type de valeur ait un type d'objet de segment correspondant. Un emplacement de stockage de type structure contient simplement une concaténation des champs publics et privés du type, et le type de segment de mémoire est généré automatiquement selon un modèle tel que:et pour une instruction du type: Console.WriteLine ("La valeur est {0}", somePoint);
à traduire comme: boxed_Point box1 = new boxed_Point (somePoint); Console.WriteLine ("La valeur est {0}", box1);
En pratique, étant donné que les types d'emplacement de stockage et les types d'instance de segment de mémoire existent dans des univers distincts, il n'est pas nécessaire d'appeler les types d'instance de segment de mémoire comme par exemple
boxed_Int32
; puisque le système saurait quels contextes requièrent l'instance d'objet tas et lesquels nécessitent un emplacement de stockage.Certaines personnes pensent que tout type de valeur qui ne se comporte pas comme un objet devrait être considéré comme "diabolique". Je suis du même avis: puisque les emplacements de stockage des types de valeur ne sont ni des objets ni des références à des objets, l'attente qu'ils se comportent comme des objets ne doit pas être considérée comme une aide. Dans les cas où une structure peut se comporter utilement comme un objet, il n’ya rien de mal à en avoir une, mais chacune
struct
n’est en son cœur qu’une agrégation de champs publics et privés collés avec du ruban adhésif.la source