为什么枚举的方法HasFlag需要拳击?(Why Enum's HasFlag method

2019-06-26 12:52发布

我读“通过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 ,对不对?

Answer 1:

在这种情况下,之前,你甚至进入两个拳击通话需要HasFlags方法。 一个是用于解决该值类型为基本类型方法的方法调用时,另一个传递值类型作为参考类型参数。 你可以看到相同的IL如果你做var type = 1.GetType(); ,字面int 1的前盒装GetType()调用。 在方法调用的拳似乎只有当方法未在值类型定义本身覆盖,更可以在这里阅读: 请问呼吁在.NET拳击值类型结果的方法?

HasFlags需要一个Enum 参数,因此拳击将在这里发生。 你试图传递的是一个价值型变成什么期待引用类型。 为了表示值作为参考,发生拳击。

有很多的价值类型及其继承(与编译器的支持Enum / ValueType )试图解释它的时候,混淆的情况。 人们似乎认为,因为EnumValueType是值类型拳击突然不适的继承链。 如果这是真的,同样可以说的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


Answer 2:

值得一提的是一个通用的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);
    }
}


Answer 3:

Enum从继承ValueType是......一类! 因此,拳击。

注意, Enum类可以代表任何枚举,无论其基本类型是,作为装箱的值。 而一个值,如FileAttributes.Hidden将被表示为实值类型,INT。

编辑:让我们区分类型,这里的代表性。 一个int在内存中表示为32位。 它的类型从派生ValueType 。 只要你指定一个int到一个object或派生的类( ValueType类, Enum类),你拳击它,有效地改变其代表性的一类现在包含32位,加上附加的类信息。



Answer 4:

当过你传递需要对象作为参数,如在Console.WriteLine的可情况下的方法的值类型,将有一个固有的装箱操作。 杰弗里·里希特讨论了这个在你提到的同一本书的细节。

在这种情况下使用的是个Console.WriteLine String.Format方法,而这需要对象[]的参数数组。 所以,你的布尔,将转换为对象,所以因此你会得到一个装箱操作。 您可以通过调用的ToString()的布尔避免这种情况。



Answer 5:

有参与该呼叫,而不只是一个两拳操作。 而且两者都需要一个简单的原因: Enum.HasFlag()需要键入信息 ,而不仅仅是值,对于thisflag

大多数时候,一个enum值确实只是一组位和编译器有它全部从所需要的类型信息enum方法签名代表类型。

然而,在的情况下Enum.HasFlags()它的第一件事就是打电话this.GetType()flag.GetType()并确保它们是相同的。 如果你想在无类型的版本,你会问if ((attribute & flag) != 0)而不是调用Enum.HasFlags()



Answer 6:

此外,还有比在单拳更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);


文章来源: Why Enum's HasFlag method need boxing?