I am reading "C# via CLR" and on page 380, there's a note saying the following:
Note The Enum class defines a HasFlag method defined as follows
public Boolean HasFlag(Enum flag);
Using this method, you could rewrite the call to Console.WriteLine like this:
Console.WriteLine("Is {0} hidden? {1}", file, attributes.HasFlag(FileAttributes.Hidden));
However, I recommend that you avoid the HasFlag method for this reason:
Since it takes a parameter of type Enum, any value you pass to it must be boxed, requiring a memory allocation ."
I can not understand this bolded statement -- why "
any value you pass to it must be boxed
The flag
parameter type is Enum
, which is a value type, why would there be boxing? The "any value you pass to it must be boxed" should mean boxing happens when you pass value type to parameter Enum flag
, right?
In this instance, two boxing calls are required before you even get into the HasFlags
method. One is for resolving the method call on the value type to the base type method, the other is passing the value type as a reference type parameter. You can see the same in IL if you do var type = 1.GetType();
, the literal int
1 is boxed before the GetType()
call. The boxing on method call seems to be only when methods are not overridden in the value type definition itself, more can be read here: Does calling a method on a value type result in boxing in .NET?
The HasFlags
takes an Enum
class argument, so the boxing will occur here. You are trying to pass what is a value type into something expecting a reference type. To represent values as references, boxing occurs.
There is lots of compiler support for value types and their inheritance (with Enum
/ ValueType
) that confuses the situation when trying to explain it. People seem to think that because Enum
and ValueType
is in the inheritance chain of value types boxing suddenly doesn't apply. If this were true, the same could be said of object
as everything inherits that - but as we know this is false.
This doesn't stop the fact that representing a value type as a reference type will incur boxing.
And we can prove this in IL (look for the box
codes):
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
The same can be seen when representing a value type as ValueType
, it also results in boxing:
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