type que number generic convert cast c# .net enums boxing

c# - que - ¿Por qué el método HasFlag de Enum necesita boxeo?



que es boxing en c# (6)

Estoy leyendo "C # mediante CLR" y en la página 380, hay una nota que dice lo siguiente:

Nota La clase Enum define un método HasFlag definido de la siguiente manera

public Boolean HasFlag(Enum flag);

Con este método, puede volver a escribir la llamada a Console.WriteLine de la siguiente manera:

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

Sin embargo, le recomiendo que evite el método HasFlag por este motivo:

Como toma un parámetro de tipo Enum, cualquier valor que le pase debe estar encuadrado, lo que requiere una asignación de memoria ".

No puedo entender esta declaración en negrita, ¿por qué?

cualquier valor que le pase debe estar enmarcado

El tipo de parámetro del Enum es Enum , que es un tipo de valor, ¿por qué habría boxeo? El "cualquier valor que le pase debe estar encuadrado" debe significar que el boxeo ocurre cuando pasa el tipo de valor al parámetro Enum flag , ¿verdad?


Cada vez que pasa un tipo de valor de un método que toma objeto como parámetro, como en el caso de console.writeline, habrá una operación inherente de boxeo. Jeffery Richter discute esto en detalle en el mismo libro que mencionas.

En este caso, está utilizando el método string.format de console.writeline, y eso toma una matriz params del objeto []. Así que tu bool, será lanzado a objeto, por lo tanto, obtienes una operación de boxeo. Puede evitar esto llamando a .ToString () en el bool.


Hay dos operaciones de boxeo involucradas en esta convocatoria, no solo una. Y ambos son necesarios por una simple razón: Enum.HasFlag() necesita información de tipo , no solo valores, para this y para el flag .

La mayoría de las veces, un valor enum es solo un conjunto de bits y el compilador tiene toda la información de tipo que necesita de los tipos de enum representados en la firma del método.

Sin embargo, en el caso de Enum.HasFlags() lo primero que hace es llamar this.GetType() y flag.GetType() y asegurarse de que sean idénticos. Si quisieras la versión sin tipo, estarías preguntando if ((attribute & flag) != 0) , en lugar de llamar a Enum.HasFlags() .


Enum hereda de ValueType que es ... ¡una clase! De ahí el boxeo.

Tenga en cuenta que la clase Enum puede representar cualquier enumeración, cualquiera que sea su tipo subyacente, como un valor encuadrado. Mientras que un valor como FileAttributes.Hidden se representará como tipo de valor real, int.

Editar: diferenciemos el tipo y la representación aquí. Un int se representa en la memoria como 32 bits. Su tipo deriva de ValueType . Tan pronto como asigna un int a un object o clase derivada (clase ValueType , clase Enum ), lo ValueType , cambiando efectivamente su representación a una clase que ahora contiene esos 32 bits, más información de clase adicional.


Además, hay más de un boxeo en Enum.HasFlag :

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

Mire las llamadas al método GetValue .

Actualización . Parece que MS había optimizado este método en .NET 4.5 (el código fuente se ha descargado 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.GetResourceString("Argument_EnumTypeDoesNotMatch", flag.GetType(), this.GetType())); } return InternalHasFlag(flag); } [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] private extern bool InternalHasFlag(Enum flags);


Vale la pena señalar que un HasFlag<T>(T thing, T flags) genérico HasFlag<T>(T thing, T flags) que es aproximadamente 30 veces más rápido que el método de extensión Enum.HasFlag se puede escribir en aproximadamente 30 líneas de código. Incluso se puede convertir en un método de extensión. Desafortunadamente, no es posible en C # restringir dicho método para que solo tome elementos de los tipos enumerados; en consecuencia, Intellisense mostrará el método incluso para los tipos para los que no es aplicable. Creo que si uno utiliza un idioma que no sea C # o vb.net para escribir el método de extensión, es posible que aparezca solo cuando debería, pero no estoy lo suficientemente familiarizado con otros idiomas para probar tal cosa.

internal static class EnumHelper<T1> { public static Func<T1, T1, bool> 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<T1, T1>).GetMethod("Overlaps", types); if (method == null) method = typeof(T1).GetMethod("Overlaps", types); if (method == null) throw new MissingMethodException("Unknown type of enum"); TestOverlapProc = (Func<T1, T1, bool>)Delegate.CreateDelegate(typeof(Func<T1, T1, bool>), method); return TestOverlapProc(p1, p2); } } static class EnumHelper { public static bool Overlaps<T>(this T p1, T p2) where T : struct { return EnumHelper<T>.TestOverlapProc(p1, p2); } }


En este caso, se requieren dos llamadas de boxeo antes de ingresar al método HasFlags . Una es para resolver la llamada al método en el tipo de valor para el método de tipo base, la otra está pasando el tipo de valor como un parámetro de tipo de referencia. Puede ver lo mismo en IL si lo hace var type = 1.GetType(); , literal int 1 está GetType() antes de la llamada GetType() . La llamada al método de boxeo parece ser solo cuando los métodos no se anulan en la definición del tipo de valor en sí, se puede leer más aquí: ¿Llamar a un método en un tipo de valor resulta en el boxeo en .NET?

El HasFlags toma un argumento de la clase Enum , por lo que el boxeo se producirá aquí. Está intentando pasar lo que es un tipo de valor a algo que espera un tipo de referencia. Para representar los valores como referencias, se produce el boxeo.

Hay una gran cantidad de compatibilidad del compilador para los tipos de valores y su herencia (con Enum / ValueType ) que confunde la situación al intentar explicarlo. La gente parece pensar que debido a que Enum y ValueType están en la cadena de herencia de los tipos de valores, el boxeo de repente no se aplica. Si esto fuera cierto, lo mismo podría decirse del object ya que todo lo hereda, pero como sabemos, esto es falso.

Esto no detiene el hecho de que representar un tipo de valor como un tipo de referencia incurrirá en el boxeo.

Y podemos probar esto en IL (busque los códigos de box ):

class Program { static void Main(string[] args) { var f = Fruit.Apple; var result = f.HasFlag(Fruit.Apple); Console.ReadLine(); } } [Flags] enum Fruit { Apple } .method private hidebysig static void Main ( string[] 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 string [mscorlib]System.Console::ReadLine() IL_001a: pop IL_001b: ret } // end of method Program::Main

Lo mismo se puede ver al representar un tipo de valor como ValueType , también resulta en el boxeo:

class Program { static void Main(string[] args) { int i = 1; ValueType v = i; Console.ReadLine(); } } .method private hidebysig static void Main ( string[] 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 string [mscorlib]System.Console::ReadLine() IL_000f: pop IL_0010: ret } // end of method Program::Main