Question

Modifier : Commentaires en bas. En outre, cette .


Voici ce que est le genre de me confondre. Je crois comprendre que si j'ai un ENUM comme ça ...

enum Animal
{
    Dog,
    Cat
}

... ce que je l'ai fait essentiellement est défini un type de valeur appelé Animal avec deux valeurs définies, Dog et Cat. Ce Type dérive de type de référence System.Enum (ce qui les types de valeurs ne peuvent normalement faire, du moins pas en C # -mais qui est permis dans ce cas), et dispose d'une installation de coulée et-vient vers / à partir des valeurs de int.

Si la façon dont je viens de décrire le type de ENUM ci-dessus était vrai, alors j'attendre le code suivant pour lancer une 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 Unbox un type de valeur de System.Object comme autre chose que son type exact. Alors, comment est possible ci-dessus? Il est comme si le type de Animal est un int (pas seulement convertibles à int) et est un Enum (pas seulement convertibles à Enum) en même temps. Est-ce l'héritage multiple? Est-ce que System.Enum en quelque sorte hériter de System.Int32 (quelque chose que je ne l'aurais pas devrait être possible)?

Modifier : Il ne peut être ni de ce qui précède. Le code suivant illustre ce (je pense) de façon concluante:

object animal = Animal.Dog;

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

Les sorties ci-dessus:

True
False

la documentation MSDN sur énumérations et la spécification C # faire usage du terme « type sous-jacent »; mais je ne sais pas ce que cela signifie, je n'ai jamais entendu utilisé en référence à autre chose que énumérations. Que signifie "type sous-jacent" en fait mean


Alors, est-ce encore une autre cas qui obtient un traitement spécial de la CLR?

Mon argent est ce qui est le cas ... mais une réponse / explication serait bien.


Mise à jour : Damien_The_Unbeliever à condition que la référence pour répondre vraiment à cette question. L'explication se trouve dans la partition II de la spécification CLI, dans la section sur les énumérations:

  

À des fins de liaison (par exemple, pour   la localisation d'une définition de la méthode de la   Méthode de référence utilisé pour appeler)   énumérations doivent être distincts de leur   type sous-jacent. Pour tous les autres   fins, y compris la vérification et   exécution du code, un enum unboxed   interconvertit librement avec son   type sous-jacent . Énumérations peuvent être mis en boîte   à une instance en boîte correspondant   Le type, mais ce type est pas même   comme le type boxed du sous-jacent   le type, alors la boxe ne perd pas la   type original de l'ENUM.

Modifier (encore une fois!): Attendez, en fait, je ne sais pas que j'ai lu ce droit la première fois. Peut-être qu'il ne 100% expliquer le comportement unboxing spécialisé lui-même (si je quitte la réponse de Damien acceptée, car elle a versé beaucoup de lumière sur cette question). Je continuerai à la recherche dans ce ...


Une autre Modifier : l'homme, puis réponse yodaj007 jeté moi une autre boucle une certaine façon un ENUM est pas exactement la même que celle d'un int;.? encore une int peut être affecté à une variable enum sans cast Buh

Je pense que tout cela est en fin de compte par illuminées réponse de Hans , qui est la raison pour laquelle je l'ai accepté. (Désolé, Damien!)

Était-ce utile?

La solution

Oui, un traitement spécial. Le compilateur JIT est très conscient de la façon dont le travail boxed types de valeur. Ce qui est en général ce qui rend les types de valeurs agissant un peu schizoïde. Boxe implique la création d'une valeur System.Object qui se comporte exactement de la même manière comme une valeur d'un type de référence. À ce moment-là, les valeurs de type valeur ne se comportent comme des valeurs lors de l'exécution ne. Ce qui permet, par exemple, d'avoir une méthode virtuelle comme ToString (). L'objet a un pointeur en boîte de table de méthode, tout comme les types de référence font.

Le compilateur JIT connaît les tables de méthode pointeurs pour les types de valeur comme int et bool avant. Boxe et unboxing pour eux est très efficace, il faut, mais une poignée d'instructions de code machine. Ce besoin d'être de retour efficace dans .NET 1.0 pour le rendre compétitif. très une partie importante de c'est la restriction qu'une valeur de type de valeur ne peut être Unboxed au même type. Cela évite la gigue d'avoir à générer une instruction switch massive qui invoque le code de conversion correcte. Tout ce qu'il a à faire est de vérifier le pointeur de la table de méthode dans l'objet et vérifier qu'il est le type attendu. Et copier la valeur de l'objet directement. Notable est peut-être que cette restriction n'existe pas dans VB.NET, son opérateur CType () qui en fait le code à générer une fonction d'assistance qui contient cette grande instruction switch.

Le problème avec les types ENUM est que cela ne peut pas fonctionner. Énumérations peuvent avoir un autre type GetUnderlyingType (). En d'autres termes, la valeur unboxed a différentes tailles afin de copier simplement la valeur de l'objet boxed ne peut pas fonctionner. Tout à fait conscient, la gigue ne en ligne le code unboxing plus, il génère un appel à une fonction d'assistance dans le CLR.

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

Quelle est la raison pour laquelle est déclarée Enum en tant que classe. Son logique comportement est d'un type de référence, dérivés types de ENUM peuvent être coulés d'un à l'autre. Avec la restriction susmentionnée sur la compatibilité de type sous-jacent. Les valeurs d'un type ENUM ont cependant très bien le comportement d'une valeur de type de valeur. Ils ont une sémantique de copie et le comportement de boxe.

Autres conseils

énumérations sont spécialement traitées par le CLR. Si vous voulez entrer dans les détails sordides, vous pouvez télécharger le MS II Partition spec. Dans ce document, vous trouverez que énumérations:

  

Enums obey restrictions supplémentaires   au-delà de ceux d'autres types de valeurs.   Énumérations ne contiennent que les domaines tels que   membres (ils ne définissent même   ou de type initializers instance   constructeurs); ils ne doivent pas   mettre en oeuvre toutes les interfaces; ils   doit avoir la mise en page de champ automatique   (§10.1.2); ils doivent avoir exactement un   champ d'instance et il est du type sous-jacent   l'énumération; tous les autres champs sont   statique et littéral (§16.1);

Alors que de la façon dont ils peuvent hériter de System.Enum, mais qui ont un « sous-jacente » de type -. C'est le champ d'instance unique, ils sont autorisés à avoir

Il y a aussi une discussion sur le comportement de boxe, mais il ne décrit pas explicitement unboxing du type sous-jacent, que je peux voir.

partition I, 8.5.2 stipule que les énumérations sont « un autre nom pour un type existant » mais « [f] ou aux fins de signatures correspondant, un ENUM ne doit pas être le même que le type sous-jacent ».

Partition II, 14.3 expose: « . Pour toutes autres fins, y compris la vérification et l'exécution du code, un ENUM Unboxed librement interconvertit avec son type sous-jacent énumérations peuvent être mis en boîte à un type d'instance en boîte correspondant, mais ce type n'est pas le même comme le type boxed du type sous-jacent, de sorte que la boxe ne perd pas le type original de la ENUM. "

Partition III, 4,32 explique le comportement unboxing:. « Le type de type de valeur contenu dans obj doit être assignation compatible avec valuetype [ Note:. Cet effet le comportement des types enum, voir la partition II.14.3 note finale] »

Ce que je veux souligner ici est 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 d'un type d'énumération), un autre nom pour un type existant. Aux fins de signatures correspondant, un ENUM ne doit pas être le même que le type sous-jacent. Les instances d'un cependant ENUM, sont assignables au type sous-jacent, et vice versa. C'est pas coulé (voir §8.3.3) ou contrainte (voir §8.3.2) est nécessaire pour convertir de l'ENUM au type sous-jacent, ils ne sont pas nécessaires du type sous-jacent à l'ENUM. Un ENUM est beaucoup plus limité qu'un vrai type, comme suit:

     

Le type sous-jacent est un type entier intégré. Énumérations doit provenir de System.Enum, d'où ils sont les types de valeur. Comme tous les types de valeur, ils doivent être scellés (voir §8.9.9).

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

Cette déclaration sera faux à cause de la deuxième phrase:

x is int

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

De la page 46:

  

types sous-jacents - dans les énumérations CTS sont d'autres noms pour les types existants (§8.5.2), appelé leur type sous-jacent. Sauf pour la correspondance de signature (§8.5.2) énumérations sont traités comme un type sous-jacent. Ce sous-ensemble est l'ensemble des types de stockage avec les énumérations enlevés.

Retour à mon Foo ENUM plus tôt. Cette déclaration fonctionnera:

Foo x = (Foo)5;

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

.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 
}

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

Extrait MSDN :

  

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

Ainsi, le casting est possible, mais vous devez forcer:

  

Les Précise de type sous-jacent la quantité de stockage est allouée pour chaque recenseur. Cependant, un casting explicite est nécessaire pour convertir type ENUM à un type intégral.

Lorsque vous votre ENUM en boîte que object, l'objet animal est dérivé de System.Enum (le type réel est connu lors de l'exécution) de sorte qu'il est en fait un int, de sorte que la distribution est valide.

  • retourne (animal is Enum) true: Pour cette raison, vous pouvez unbox animal dans un Enum ou d'un événement dans un int faire un casting explicite
  • .
  • retourne (animal is int) false: L'opérateur is (sous contrôle de type général) ne vérifie pas le type sous-jacent pour énumérations. En outre, pour cette raison, vous devez faire un casting explicite pour convertir Enum int.

Alors que les types de ENUM sont héritées de System.Enum, toute conversion entre eux ne sont pas directement, mais un de boxe / unboxing. De C # 3.0 Spécification:

  

Type Un dénombrement est un type distinct   avec des constantes nommées. Chaque   type d'énumération est un sous-jacent   type, qui doit être octet, sbyte,   bref, ushort, int, uint, long ou   ulong. L'ensemble des valeurs de la   type d'énumération est la même que la   ensemble de valeurs du type sous-jacent.   Les valeurs du type d'énumération ne sont pas   limitée aux valeurs du nom   constantes. Énumération types sont   défini par énumération   déclarations

Ainsi, alors que votre classe animale est dérivé de System.Enum, il est en fait un int. BTW, une autre chose étrange est System.Enum est dérivé de System.ValueType, mais il est encore un type de référence.

Le type sous-jacent d'un Enum est le type utilisé pour stocker la valeur des constantes. Dans votre exemple, même si vous ne l'avez pas explicitement défini les valeurs, C # fait ceci:

enum Animal : int
{
    Dog = 0,
    Cat = 1
}

En interne, Animal est composé de deux constantes avec les valeurs entières 0 et 1. C'est pourquoi vous pouvez convertir explicitement un entier à un Animal et un Animal à un nombre entier. Si vous passez Animal.Dog à un paramètre qui accepte un Animal, ce que vous faites vraiment est passer la valeur entière 32 bits de Animal.Dog (dans ce cas, 0). Si vous donnez Animal un nouveau type sous-jacent, les valeurs sont stockées sous forme de ce type.

Pourquoi ne pas ... il est tout à fait valable, par exemple, pour une structure de tenir un int interne, et être converti en int avec un opérateur explicite coulé ... permet de simuler un Enum:

interface IEnum { }

struct MyEnumS : IEnum
{
    private int inner;

    public static explicit operator int(MyEnumS val)
    {
        return val.inner;
    }

    public static explicit operator MyEnumS(int val)
    {
        MyEnumS result;
        result.inner = val;
        return result;
    }

    public static readonly MyEnumS EnumItem1 = (MyEnumS)0;
    public static readonly MyEnumS EnumItem2 = (MyEnumS)2;
    public static readonly MyEnumS EnumItem3 = (MyEnumS)10;

    public override string ToString()
    {
        return inner == 0 ? "EnumItem1" :
            inner == 2 ? "EnumItem2" :
            inner == 10 ? "EnumItem3" :
            inner.ToString();
    }
}

Cette structure peut être utilisé tout à fait de la même manière une struct peut ... bien sûr, si vous essayez de refléter le type, et appelez la propriété IsEnum il retournera false.

regard Let à une comparaison d'utilisation, avec l'équivalent ENUM:

enum MyEnum
{
    EnumItem1 = 0,
    EnumItem2 = 2,
    EnumItem3 = 10,
}

En comparant les usages:

Version Struct:

var val = MyEnum.EnumItem1;
val = (MyEnum)50;
val = 0;
object obj = val;
bool isE = obj is MyEnum;
Enum en = val;

Version Enum:

var valS = MyEnumS.EnumItem1;
valS = (MyEnumS)50;
//valS = 0; // cannot simulate this
object objS = valS;
bool isS = objS is MyEnumS;
IEnum enS = valS;

Certaines opérations ne peuvent pas être simulés, mais ce montre tous ce que je voulais dire ... énumérations sont spéciaux, oui ... combien spécial? pas autant! =)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top