我读“通过CLR C#”和380页上,有一个纸条,上面写着以下内容:
注意枚举类定义了一个方法HasFlag定义如下
public Boolean HasFlag(Enum flag);
使用这种方法,你可以重写这样调用Console.WriteLine:
Console.WriteLine("Is {0} hidden? {1}", file, attributes.HasFlag(FileAttributes.Hidden));
不过,我建议你避免这个原因,HasFlag方法:
由于需要枚举类型的参数,你传递给它的任何值必须盒装,需要内存分配 “。
我无法理解这样的粗体声明 - 为什么“
您传递给它的任何值必须被装箱
该flag
参数类型是Enum
,这是值类型,为什么会有拳击? 在“您传递给它的任何值必须盒装”应该意味着当您传递值类型参数拳击发生Enum flag
,对不对?
在这种情况下,之前,你甚至进入两个拳击通话需要HasFlags
方法。 一个是用于解决该值类型为基本类型方法的方法调用时,另一个传递值类型作为参考类型参数。 你可以看到相同的IL如果你做var type = 1.GetType();
,字面int
1的前盒装GetType()
调用。 在方法调用的拳似乎只有当方法未在值类型定义本身覆盖,更可以在这里阅读: 请问呼吁在.NET拳击值类型结果的方法?
该HasFlags
需要一个Enum
类参数,因此拳击将在这里发生。 你试图传递的是一个价值型变成什么期待引用类型。 为了表示值作为参考,发生拳击。
有很多的价值类型及其继承(与编译器的支持Enum
/ ValueType
)试图解释它的时候,混淆的情况。 人们似乎认为,因为Enum
和ValueType
是值类型拳击突然不适的继承链。 如果这是真的,同样可以说的object
是一切继承了-但我们知道这是假的。
这并不能阻止的事实,表示值类型为引用类型会招致拳。
我们可以在IL(寻找证明了这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
同样可以表示值类型作为时可以看到ValueType
,它也导致拳击:
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
值得一提的是一个通用的HasFlag<T>(T thing, T flags)
其比快约30倍Enum.HasFlag
扩展方法可以在大约30行的代码被写入。 它甚至可以做成一个扩展方法。 不幸的是,它不是在C#中可能限制这种方法只需要枚举类型的东西; 因此,智能感知会弹出方法即使对于类型,它是不适用的。 我认为,如果一个比使用C#或vb.net写的扩展方法其他一些语言有可能使其弹出只有当它应该,但我不熟悉不够用其他语言来尝试这样的事情。
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);
}
}
Enum
从继承ValueType
是......一类! 因此,拳击。
注意, Enum
类可以代表任何枚举,无论其基本类型是,作为装箱的值。 而一个值,如FileAttributes.Hidden
将被表示为实值类型,INT。
编辑:让我们区分类型,这里的代表性。 一个int
在内存中表示为32位。 它的类型从派生ValueType
。 只要你指定一个int
到一个object
或派生的类( ValueType
类, Enum
类),你拳击它,有效地改变其代表性的一类现在包含32位,加上附加的类信息。
当过你传递需要对象作为参数,如在Console.WriteLine的可情况下的方法的值类型,将有一个固有的装箱操作。 杰弗里·里希特讨论了这个在你提到的同一本书的细节。
在这种情况下使用的是个Console.WriteLine String.Format方法,而这需要对象[]的参数数组。 所以,你的布尔,将转换为对象,所以因此你会得到一个装箱操作。 您可以通过调用的ToString()的布尔避免这种情况。
有参与该呼叫,而不只是一个两拳操作。 而且两者都需要一个简单的原因: Enum.HasFlag()
需要键入信息 ,而不仅仅是值,对于this
和flag
。
大多数时候,一个enum
值确实只是一组位和编译器有它全部从所需要的类型信息enum
方法签名代表类型。
然而,在的情况下Enum.HasFlags()
它的第一件事就是打电话this.GetType()
和flag.GetType()
并确保它们是相同的。 如果你想在无类型的版本,你会问if ((attribute & flag) != 0)
而不是调用Enum.HasFlags()
此外,还有比在单拳更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;
}
看GetValue
方法调用。
更新 。 貌似MS已经优化在.NET 4.5此方法(在源代码已经从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);