41 votes

Comment se fait-il qu'un enum dérive de System.Enum et soit un entier en même temps ?

Modifier : Commentaires en bas de page. Aussi, este .


Voilà ce qui me perturbe un peu. Si je comprends bien, si j'ai un enum comme celui-ci...

enum Animal
{
    Dog,
    Cat
}

...ce que j'ai fait essentiellement est de définir une type de valeur appelé Animal avec deux valeurs définies, Dog y Cat . Ce type dérive de la type de référence System.Enum (ce que les types de valeur ne peuvent normalement pas faire - du moins pas en C# - mais qui est autorisé dans ce cas), et dispose d'une fonction permettant de faire des allers-retours vers/depuis int valeurs.

Si la façon dont j'ai décrit le type d'enum ci-dessus était vraie, alors je m'attendrais à ce que le code suivant lève une erreur de type InvalidCastException :

public class Program
{
    public static void Main(string[] args)
    {
        // Box it.
        object animal = Animal.Dog;

        // Unbox it. How are these both successful?
        int i = (int)animal;
        Enum e = (Enum)animal;

        // Prints "0".
        Console.WriteLine(i);

        // Prints "Dog".
        Console.WriteLine(e);
    }
}

Normalement, vous ne pouvez pas débloquer un type de valeur de System.Object comme autre chose que son type exact . Comment cela est-il possible ? C'est comme si le Animal type est un int (pas seulement convertible a int ) et est un Enum (pas seulement convertible a Enum ) en même temps. S'agit-il d'un héritage multiple ? Est-ce que System.Enum héritent en quelque sorte de System.Int32 (ce que je n'aurais pas cru possible) ?

Modifier : Ça ne peut être ni l'un ni l'autre. Le code suivant le démontre (je pense) de manière concluante :

object animal = Animal.Dog;

Console.WriteLine(animal is Enum);
Console.WriteLine(animal is int);

Les sorties ci-dessus :

True
False

Les deux sites la documentation MSDN sur les énumérations et la spécification C# utilisent le terme "type sous-jacent", mais je ne sais pas ce que cela signifie et je n'ai jamais entendu ce terme utilisé en référence à autre chose que des enums. Que signifie "type sous-jacent" en réalité moyenne ?


Alors, est-ce que c'est encore un autre qui bénéficie d'un traitement spécial de la part du CLR ?

Je parie que c'est le cas... mais une réponse/explication serait la bienvenue.


Mise à jour : Damien_Le_incrédule a fourni la référence pour répondre réellement à cette question. L'explication se trouve dans la Partition II de la spécification CLI, dans la section sur les enums :

À des fins de liaison (par exemple, pour localiser une définition de méthode à partir de la référence de la méthode utilisée pour l'appeler) les enums doivent être distincts de leur type sous-jacent. Pour toutes les autres fins, y compris la vérification et l'exécution l'exécution du code, un enum sans boîte s'interconvertit librement avec son type sous-jacent . Les enums peuvent être mis en boîte à un type d'instance boxé correspondant correspondant, mais ce type est pas le même que le type encadré du type sous-jacent sous-jacent, de sorte que la mise en boîte ne perd pas le type original de l'enum.

Modifier (encore ? !) : Attends, en fait, je ne sais pas si j'ai bien lu la première fois. Peut-être que cela n'explique pas à 100% le comportement de déballage spécialisé lui-même (bien que je laisse la réponse de Damien comme acceptée, car elle a apporté beaucoup de lumière sur cette question). Je vais continuer à chercher dans ce domaine...


Une autre édition : Homme, alors La réponse de yodaj007 m'a jeté dans une autre boucle. D'une certaine manière, un enum n'est pas exactement le même qu'un int ; pourtant un int peut être assigné à une variable enum sans casting ? Buh ?

Je pense que tout cela est finalement éclairé par Réponse de Hans c'est pourquoi je l'ai accepté. (Désolé, Damien !)

24voto

Hans Passant Points 475940

Oui, un traitement spécial. Le compilateur JIT est parfaitement conscient de la façon dont les types de valeurs encadrés fonctionnent. C'est en général ce qui rend les types de valeurs un peu schizoïdes. La mise en boîte consiste à créer une valeur System.Object qui se comporte exactement de la même manière qu'une valeur de type référence. À ce moment-là, les valeurs de type valeur ne se comportent plus comme des valeurs au moment de l'exécution. Ce qui permet, par exemple, d'avoir une méthode virtuelle comme ToString(). L'objet boxé possède un pointeur de table de méthode, tout comme les types de référence.

Le compilateur JIT connaît d'emblée les pointeurs des tables de méthodes pour les types de valeurs comme int et bool. Le boxing et le unboxing pour eux est très efficace, il ne nécessite qu'une poignée d'instructions de code machine. Il fallait que ce soit efficace dès la version 1.0 de .NET pour qu'il soit compétitif. A très Une partie importante de cela est la restriction selon laquelle une valeur de type valeur ne peut être décomposée que dans le même type. Cela évite à la gigue de devoir générer une instruction switch massive qui invoque le code de conversion correct. Tout ce qu'il a à faire est de contrôler le pointeur de la table des méthodes dans l'objet et de vérifier qu'il s'agit du type attendu. Et de copier directement la valeur hors de l'objet. Il est peut-être intéressant de noter que cette restriction n'existe pas dans VB.NET, son opérateur CType() génère en fait du code vers une fonction d'aide qui contient cette grosse instruction de commutation.

Le problème avec les types Enum est que cela ne peut pas fonctionner. Les Enums peuvent avoir un type GetUnderlyingType() différent. En d'autres termes, la valeur non encadrée a des tailles différentes, de sorte que la simple copie de la valeur de l'objet encadré ne peut pas fonctionner. Bien conscient du fait que la gigue ne met plus en ligne le code d'unboxing, elle génère un appel à une fonction d'aide dans le CLR.

Cette aide s'appelle JIT_Unbox(), vous pouvez trouver son code source dans le source de SSCLI20, clr/src/vm/jithelpers.cpp. Vous verrez qu'il traite spécialement les types enum. Il est permissif, il permet l'unboxing d'un type enum à un autre. Mais seulement si le type sous-jacent est le même, vous obtenez une InvalidCastException si ce n'est pas le cas.

C'est aussi la raison pour laquelle Enum est déclaré comme une classe. Son site logique est d'un type de référence, les types d'énumération dérivés peuvent être convertis en un autre type. Avec la restriction susmentionnée concernant la compatibilité du type sous-jacent. Les valeurs d'un type enum ont cependant un comportement très proche de celui d'une valeur de type value. Elles ont une sémantique de copie et un comportement de mise en boîte.

9voto

Damien_The_Unbeliever Points 102139

Les Enums sont spécialement traités par le CLR. Si vous voulez entrer dans les détails, vous pouvez télécharger le manuel de l'utilisateur du CLR. MS Partition II spec. Dans celle-ci, vous trouverez que Enums :

Les Enums obéissent à des restrictions supplémentaires supplémentaires par rapport aux autres types de valeurs. Les Enums ne doivent contenir que des champs comme comme membres (ils ne doivent même pas définir initialisateurs de type ou d'instance constructeurs d'instance) ; ils ne doivent pas implémenter d'interfaces ; elles doivent avoir une disposition automatique des champs (§10.1.2) ; ils doivent avoir exactement une d'instance et celui-ci doit être du type sous-jacent de l'enum. l'énumération ; tous les autres champs doivent être statiques et littéraux (§16.1) ;

C'est ainsi qu'ils peuvent hériter de System.Enum, mais avoir un type "sous-jacent" - c'est le seul champ d'instance qu'ils sont autorisés à avoir.

Il y a aussi une discussion sur le comportement des boîtes, mais elle ne décrit pas explicitement le débridage vers le type sous-jacent, à ce que je vois.

4voto

Amy Points 8019

Ce que je note ici est tiré de la page 38 de ECMA-335 (Je vous suggère de le télécharger juste pour l'avoir) :

Le CTS prend en charge un enum (également connu sous le nom de type énumération), un nom alternatif pour un type existant. Aux fins de la correspondance des signatures, un enum ne doit pas être le même que le type sous-jacent. Toutefois, les instances d'une énumération doivent pouvoir être affectées au type sous-jacent, et vice versa. Autrement dit, aucun cast (voir §8.3.3) ou coercition (voir §8.3.2) n'est nécessaire pour convertir l'enum en type sous-jacent, ni du type sous-jacent en enum. Une énumération est considérablement plus restreinte qu'un vrai type, comme suit :

Le type sous-jacent doit être un type d'entier intégré. Les Enums doivent dériver de System.Enum, ce qui en fait des types de valeurs. Comme tous les types de valeurs, ils doivent être scellés (voir §8.9.9).

enum Foo { Bar = 1 }
Foo x = Foo.Bar;

Cette affirmation sera fausse à cause de la deuxième phrase :

x is int

Ils sont le même (un alias), mais leur signature n'est pas la même. La conversion vers et depuis un int n'est pas un casting.

De la page 46 :

Types sous-jacents - dans le CTS, les énumérations sont des noms alternatifs pour les types existants (§8.5.2), appelés leur type sous-jacent. Sauf pour la correspondance des signatures (§8.5.2), les énumérations sont traitées comme leur type sous-jacent. Ce sous-ensemble est l'ensemble des types de stockage sans les énumérations.

Retournez à mon enum Foo plus tôt. Cette déclaration fonctionnera :

Foo x = (Foo)5;

Si vous inspectez le code IL généré de ma méthode Main dans Reflector :

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 1
.locals init (
    [0] valuetype ConsoleTesting.Foo x)
L_0000: nop 
L_0001: ldc.i4.5 
L_0002: stloc.0 
L_0003: call string [mscorlib]System.Console::ReadLine()
L_0008: pop 
L_0009: ret 
}

Notez qu'il n'y a pas de casting. ldc se trouve à la page 86. Il charge une constante. i4 se trouve à la page 151, indiquant que le type est un entier de 32 bits. Il n'y a pas de cast !

4voto

Stephen Cleary Points 91731

L'article 8.5.2 de la Partition I stipule que les enums sont "un nom alternatif pour un type existant" mais "[p]our les besoins de correspondance des signatures, un enum ne doit pas être le même que le type sous-jacent".

Le paragraphe 14.3 de la Partition II explique : "Pour toutes les autres fins, y compris la vérification et l'exécution du code, un enum non boxé interconvertit librement avec son type sous-jacent. Les enums peuvent être boxés vers un type d'instance boxé correspondant, mais ce type n'est pas le même que le type boxé du type sous-jacent, de sorte que le boxage ne perd pas le type original de l'enum."

La partition III, 4.32 explique le comportement du déballage : "Le type de type de valeur contenu dans obj. doit être compatible avec les affectations type de valeur . [Note : Ceci affecte le comportement avec les types enum, voir la Partition II.14.3. fin de la note]".

3voto

Daniel Peñalba Points 8548

Extrait de MSDN:

La valeur par défaut type sous-jacent de l'énumération des éléments est de type int. Par défaut, le premier agent recenseur a la valeur 0, et la valeur de chaque agent recenseur est augmenté de 1.

Ainsi, le casting est possible, mais vous avez besoin de forcer:

Le type sous-jacent spécifie la quantité de stockage est alloué pour chaque agent recenseur. Cependant, un cast explicite est nécessaire pour convertir à partir d'un type enum à un type intégral.

Lorsque vous de la boîte de votre enum en object, l'animal objet est dérivé de l' System.Enum (le type réel est connu au moment de l'exécution) c'est donc en fait un int, de sorte que le cast est valide.

  • (animal is Enum) retours true: Pour cette raison, vous pouvez unbox animal dans un Enum ou un événement dans un int de faire un cast explicite.
  • (animal is int) retours false: L' is de l'opérateur (en général de type case) ne vérifie pas le type sous-jacent pour les Énumérations. Aussi, pour cette raison, vous avez besoin de faire un cast explicite pour convertir Enum type int.

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