39 votes

Pourquoi les types de valeurs .NET sont-ils scellés ?

Il n'est pas possible d'hériter d'un struct C#. La raison n'est pas évidente pour moi :

  • Il est évident que vous ne pouvez pas avoir un type de référence qui hérite d'un type de valeur ; cela ne fonctionnerait pas.
  • Il ne semble pas raisonnable d'hériter d'un des types primitifs (Int32, Double, Char, etc.).
  • Il faudrait pouvoir appeler des méthodes (non virtuelles) sur la base en utilisant une instance dérivée. Vous pourriez faire un cast d'une structure dérivée vers la base, puisqu'elles recouvriraient la même mémoire. Je suppose que le casting d'une base vers une structure dérivée ne fonctionnerait pas, puisque vous ne pourriez pas connaître le type de la structure dérivée au moment de l'exécution.
  • Je vois que vous ne pouvez pas implémenter de méthodes virtuelles dans votre hiérarchie de classes, puisque les types de valeurs ne peuvent pas avoir de membres virtuels.

Je me demande s'il s'agit d'une limitation technique du CLR ou de quelque chose que le compilateur C# vous empêche de faire ?

Edit : Les types de valeurs ne peuvent pas avoir de méthodes virtuelles, et je réalise que cette limitation exclut la plupart des scénarios dans lesquels vous voudriez utiliser l'héritage. Mais il reste l'héritage en tant qu'agrégation. Imaginez un Shape struct avec un Colour domaine : Je peux écrire du code qui accepte n'importe quelle structure dérivée de Shape et d'accéder à son Colour même si je ne pourrai jamais écrire un champ virtuel Shape.Draw méthode.

Je peux penser à un scénario qui serait brisé par des types de valeurs non scellés. Les types de valeurs sont censés implémenter Equals et GetHashCode correctement ; même si ces deux méthodes sur System.Object sont virtuels, ils sont appelés de manière non virtuelle sur les types de valeurs. Même si les types de valeurs n'étaient pas scellés, quelqu'un qui écrirait une structure dérivée d'une autre ne pourrait pas écrire sa propre implémentation de ces deux méthodes et espérer qu'elles soient appelées correctement.

Je dois préciser que je ne suggère pas que je devrais pouvoir hériter de structs dans mon propre code. Ce que j'essaie de faire, cependant, c'est de deviner pourquoi cette odeur de code particulière est interdite par .NET.

Edit 2 : Je viens de repérer cette question très similaire La réponse à cette question est effectivement "parce que les tableaux de types de valeurs ne fonctionneraient pas".

53voto

Konrad Rudolph Points 231505

La raison en est que la plupart des techniques d'héritage sont liées au polymorphisme d'exécution (fonctions virtuelles) et que celles-ci ne fonctionnent pas sur les types de valeurs : pour que le polymorphisme d'exécution ait un sens, les objets doivent être traités comme des références - ceci n'est pas spécifique à .NET non plus, c'est simplement un détail technique de la façon dont les fonctions virtuelles sont implémentées.

Les types de valeur constituent une exception à la règle de .NET, précisément pour permettre des objets légers qui ne nécessitent pas d'indirection via des références. Le polymorphisme d'exécution ne fonctionne donc pas pour eux et la plupart des aspects de l'héritage perdent leur sens.

(Il y a une exception : un objet de type valeur peut être encadré, ce qui permet d'utiliser des méthodes virtuelles héritées de l'option System.Object pour être appelé).

Pour répondre à l'un de vos points :

  • Vous pouvez effectuer un casting à partir d'une structure dérivée vers la base, puisqu'elles recouvrent la même mémoire.

Non, ce n'est pas possible - le moulage d'un type de valeur serait copie sa valeur. Nous n'avons pas affaire à des références ici, donc pas de chevauchement en mémoire. Le transfert d'un type de valeur vers son type de base n'a donc aucun sens (encore une fois, à moins que nous ne parlions de la conversion vers le type de base). object qui effectue réellement la boxe sous le capot, et également fonctionne sur un copie de la valeur).

Toujours pas clair ? Prenons un exemple.

Disons que nous avons l'hypothétique struct Shape et, en héritant d'elle, la struct Circle . Shape définit une Draw (qui accepte un Graphics objet). Supposons maintenant que nous voulions dessiner une forme sur un canevas. Bien entendu, cela fonctionne parfaitement bien :

var circle = new Circle(new Point(10, 10), 20);
circle.Draw(e.Graphics); // e.Graphics = graphics object of our form.

- Mais ici, nous n'utilisons pas du tout l'héritage. Pour utiliser l'héritage, imaginez plutôt ce qui suit DrawObject méthode d'aide :

void DrawObject(Shape shape, Graphics g) {
    // Do some preparation on g.
    shape.Draw(g);
}

Et nous l'appelons ailleurs avec un Circle :

var circle = new Circle(new Point(10, 10), 20);
DrawObject(circle, e.Graphics);

- Et, ka-blam - ce code ne dessine pas de cercle. Pourquoi ? Parce que lorsque nous passons le cercle à la fonction DrawObject nous faisons deux choses :

  • Nous copie il.
  • Nous tranche il, c'est-à-dire l'objet shape n'est plus vraiment un objet Circle - ni l'original ni une copie. Au contraire, son Circle a été "découpée" lors de la copie et seule la portion Shape la partie restante. shape.Draw appelle maintenant le Draw méthode de Shape et non de Circle .

En C++, vous pouvez effectivement provoquer ce comportement. Pour cette raison, la POO en C++ ne fonctionne que sur les pointeurs et les références, et non sur les types de valeurs directement. Et pour la même raison, .NET n'autorise que l'héritage des types de référence, car vous ne pourriez pas l'utiliser pour les types de valeur de toute façon.

Remarquez que le code ci-dessus fait fonctionnent en .NET si Shape est une interface. En d'autres termes, une référence type. La situation est maintenant différente : votre circle l'objet sera toujours être copié mais il sera également encadré dans une référence.

Maintenant, .NET pourrait permettent théoriquement d'hériter d'un struct d'un class . Le code ci-dessus fonctionnerait alors tout aussi bien que si Shape étaient une interface. Mais alors, tout l'avantage d'avoir une struct en premier lieu disparaît : à toutes fins utiles (sauf pour les variables locales qui jamais sont transmises à une autre méthode, d'où l'absence d'utilité de l'héritage) votre struct se comporterait comme un type de référence immuable au lieu d'un type de valeur.

9voto

logicnp Points 4509

Extrait de l'ECMA 335 : Les types de valeurs seront scellés afin d'éviter les complications liées au découpage des valeurs. Les règles plus restrictives spécifiées ici permettent une implémentation plus efficace sans compromettre sérieusement la compromettre la fonctionnalité.

Je ne sais pas ce que signifie "value slicing", mais je suppose qu'ils sont scellés pour permettre une mise en œuvre efficace du CLR.

1voto

leppie Points 67289

Vous pourriez avoir une forme limitée d'héritage en utilisant des paramètres de type générique sur les types de valeur.

-1voto

user215303 Points 121

Parce que chaque instance de type valeur a une taille différente et est stockée sur la pile. Ainsi, si vous écrivez "Base = Dérivé" où "Base" et "Dérivé" sont des types de valeurs, vous allez corrompre la pile.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X