Pourquoi la méthode HasFlag d’Enum a-t-elle besoin de boxe?

Je lis “C # via CLR” et à la page 380, il y a une note disant ce qui suit:

Remarque la classe Enum définit une méthode HasFlag définie comme suit

public Boolean HasFlag(Enum flag);

En utilisant cette méthode, vous pouvez réécrire l’appel à Console.WriteLine comme ceci:

Console.WriteLine("Is {0} hidden? {1}", file, atsortingbutes.HasFlag(FileAtsortingbutes.Hidden));

Cependant, je vous recommande d’éviter la méthode HasFlag pour cette raison:

Puisqu’il prend un paramètre de type Enum, toute valeur que vous lui transmettez doit être encadrée, nécessitant une allocation de mémoire . ”

Je ne peux pas comprendre cette déclaration en gras – pourquoi ”

toute valeur que vous lui transmettez doit être encadrée

Le type de paramètre flag est Enum , qui est un type de valeur, pourquoi y aurait-il de la boxe? “Toute valeur que vous lui transmettez doit être encadré” doit signifier que la boxe se produit lorsque vous passez le type de valeur au paramètre Enum flag , non?

Dans ce cas, deux appels de boxe sont requirejs avant même d’avoir HasFlags méthode HasFlags . L’une consiste à résoudre l’appel de méthode sur le type de valeur vers la méthode de type de base, l’autre à transmettre le type de valeur en tant que paramètre de type de référence. Vous pouvez voir la même chose dans IL si vous faites var type = 1.GetType(); , le littéral int 1 est encadré avant l’appel de GetType() . L’appel de méthode boxing on semble être uniquement lorsque les méthodes ne sont pas écrasées dans la définition de type de valeur elle-même. Vous pouvez en lire plus ici: l’ appel d’une méthode sur un type de valeur entraîne-t-il la boxe dans .NET?

HasFlags prend un argument de classe Enum , donc la boxe aura lieu ici. Vous essayez de passer ce qui est un type de valeur à quelque chose qui attend un type de référence. Pour représenter les valeurs en tant que références, la boxe se produit.

Il existe de nombreuses possibilités de prise en charge du compilateur pour les types de valeur et leur inheritance (avec Enum / ValueType ) qui confondent la situation lorsque vous essayez de l’expliquer. Les gens semblent penser que parce Enum et ValueType de la chaîne des types de valeur d’inheritance, la boxe ne s’applique plus du tout. Si cela était vrai, on pourrait en dire autant de l’ object car tout hérite de cela – mais comme nous le soaps, cela est faux.

Cela n’arrête pas le fait que représenter un type de valeur en tant que type de référence entraînera la boxe.

Et nous pouvons le prouver en IL (recherchez les codes de box ):

 class Program { static void Main(ssortingng[] args) { var f = Fruit.Apple; var result = f.HasFlag(Fruit.Apple); Console.ReadLine(); } } [Flags] enum Fruit { Apple } .method private hidebysig static void Main ( ssortingng[] args ) cil managed { // Method begins at RVA 0x2050 // Code size 28 (0x1c) .maxstack 2 .entrypoint .locals init ( [0] valuetype ConsoleApplication1.Fruit f, [1] bool result ) IL_0000: nop IL_0001: ldc.i4.0 IL_0002: stloc.0 IL_0003: ldloc.0 IL_0004: box ConsoleApplication1.Fruit IL_0009: ldc.i4.0 IL_000a: box ConsoleApplication1.Fruit IL_000f: call instance bool [mscorlib]System.Enum::HasFlag(class [mscorlib]System.Enum) IL_0014: stloc.1 IL_0015: call ssortingng [mscorlib]System.Console::ReadLine() IL_001a: pop IL_001b: ret } // end of method Program::Main 

On peut voir la même chose quand on représente un type de valeur en tant que ValueType , cela se traduit également par la boxe

 class Program { static void Main(ssortingng[] args) { int i = 1; ValueType v = i; Console.ReadLine(); } } .method private hidebysig static void Main ( ssortingng[] args ) cil managed { // Method begins at RVA 0x2050 // Code size 17 (0x11) .maxstack 1 .entrypoint .locals init ( [0] int32 i, [1] class [mscorlib]System.ValueType v ) IL_0000: nop IL_0001: ldc.i4.1 IL_0002: stloc.0 IL_0003: ldloc.0 IL_0004: box [mscorlib]System.Int32 IL_0009: stloc.1 IL_000a: call ssortingng [mscorlib]System.Console::ReadLine() IL_000f: pop IL_0010: ret } // end of method Program::Main 

Il convient de noter qu’un générique HasFlag(T thing, T flags) qui est environ 30 fois plus rapide que la méthode d’extension Enum.HasFlag peut être écrit en environ 30 lignes de code. On peut même en faire une méthode d’extension. Malheureusement, il n’est pas possible en C # de limiter une telle méthode à ne prendre que des choses de types énumérés; par conséquent, Intellisense affichera la méthode même pour les types pour lesquels elle n’est pas applicable. Je pense que si on utilisait un langage autre que C # ou vb.net pour écrire la méthode d’extension, il serait peut-être possible de le faire apparaître seulement quand il le faudrait, mais je ne connais pas suffisamment les autres langages pour essayer une telle chose.

 internal static class EnumHelper { public static Func TestOverlapProc = initProc; public static bool Overlaps(SByte p1, SByte p2) { return (p1 & p2) != 0; } public static bool Overlaps(Byte p1, Byte p2) { return (p1 & p2) != 0; } public static bool Overlaps(Int16 p1, Int16 p2) { return (p1 & p2) != 0; } public static bool Overlaps(UInt16 p1, UInt16 p2) { return (p1 & p2) != 0; } public static bool Overlaps(Int32 p1, Int32 p2) { return (p1 & p2) != 0; } public static bool Overlaps(UInt32 p1, UInt32 p2) { return (p1 & p2) != 0; } public static bool Overlaps(Int64 p1, Int64 p2) { return (p1 & p2) != 0; } public static bool Overlaps(UInt64 p1, UInt64 p2) { return (p1 & p2) != 0; } public static bool initProc(T1 p1, T1 p2) { Type typ1 = typeof(T1); if (typ1.IsEnum) typ1 = Enum.GetUnderlyingType(typ1); Type[] types = { typ1, typ1 }; var method = typeof(EnumHelper).GetMethod("Overlaps", types); if (method == null) method = typeof(T1).GetMethod("Overlaps", types); if (method == null) throw new MissingMethodException("Unknown type of enum"); TestOverlapProc = (Func)Delegate.CreateDelegate(typeof(Func), method); return TestOverlapProc(p1, p2); } } static class EnumHelper { public static bool Overlaps(this T p1, T p2) where T : struct { return EnumHelper.TestOverlapProc(p1, p2); } } 

Enum hérite de ValueType qui est … une classe! D’où la boxe.

Notez que la classe Enum peut représenter n’importe quelle énumération, quel que soit son type sous-jacent, sous forme de valeur encadrée. Tandis qu’une valeur telle que FileAtsortingbutes.Hidden sera représentée sous la forme d’un type de valeur réelle, int.

Edit: différencions le type et la représentation ici. Un int est représenté en mémoire sur 32 bits. Son type dérive de ValueType . Dès que vous affectez un int à un object ou à une classe dérivée (classe ValueType classe Enum ), vous le boxez, en modifiant efficacement sa représentation en une classe contenant désormais ces 32 bits, ainsi que des informations supplémentaires sur la classe.

Chaque fois que vous transmettez un type de valeur d’une méthode qui prend un object comme paramètre, comme dans le cas de console.writeline, une opération de boxe inhérente se produit. Jeffery Richter en parle en détail dans le même livre que vous mentionnez.

Dans ce cas, vous utilisez la méthode ssortingng.format de console.writeline, qui utilise un tableau params d’object []. Donc, votre booléen sera lancé pour un object, donc vous obtiendrez une opération de boxe. Vous pouvez éviter cela en appelant .ToSsortingng () sur le booléen.

Il y a deux opérations de boxe impliquées dans cet appel, pas une seule. Et les deux sont nécessaires pour une raison simple: Enum.HasFlag() besoin d’ informations de type , et pas seulement de valeurs, à la fois pour this et flag .

La plupart du temps, une valeur enum n’est en réalité qu’un ensemble de bits et le compilateur dispose de toutes les informations de type nécessaires à partir des types enum représentés dans la signature de la méthode.

Cependant, dans le cas de Enum.HasFlags() la première chose à faire est d’appeler this.GetType() et de flag.GetType() et de s’assurer qu’ils sont identiques. Si vous vouliez une version sans type, vous demanderiez if ((atsortingbute & flag) != 0) , au lieu d’appeler Enum.HasFlags() .

De plus, il y a plus qu’une seule boxe dans Enum.HasFlag :

 public bool HasFlag(Enum flag) { if (!base.GetType().IsEquivalentTo(flag.GetType())) { throw new ArgumentException(Environment.GetResourceSsortingng("Argument_EnumTypeDoesNotMatch", new object[] { flag.GetType(), base.GetType() })); } ulong num = Enum.ToUInt64(flag.GetValue()); ulong num2 = Enum.ToUInt64(this.GetValue()); return (num2 & num) == num; } 

Examinez les appels de méthode GetValue .

Mettre à jour . On dirait que MS a optimisé cette méthode dans .NET 4.5 (le code source a été téléchargé à partir de referencesource):

  [System.Security.SecuritySafeCritical] public Boolean HasFlag(Enum flag) { if (flag == null) throw new ArgumentNullException("flag"); Contract.EndContractBlock(); if (!this.GetType().IsEquivalentTo(flag.GetType())) { throw new ArgumentException(Environment.GetResourceSsortingng("Argument_EnumTypeDoesNotMatch", flag.GetType(), this.GetType())); } return InternalHasFlag(flag); } [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [MethodImplAtsortingbute(MethodImplOptions.InternalCall)] private extern bool InternalHasFlag(Enum flags);