为什么Enum的HasFlag方法需要拳击?

use*_*932 13 .net c# enums boxing

我正在阅读"C#via CLR"和第380页,有一条说明如下:

注意Enum类定义HasFlag方法,定义如下

public Boolean HasFlag(Enum flag);

使用此方法,您可以重写对Console.WriteLine的调用,如下所示:

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

但是,我建议您出于这个原因避免使用HasFlag方法:

由于它采用了Enum类型的参数,因此传递给它的任何值都必须装箱,需要进行内存分配."

我无法理解这个粗犷的陈述 - 为什么"

您传递给它的任何值都必须装箱

flag参数的类型是Enum,这是一个值类型,为什么会有拳击?"传递给它的任何值必须装箱"应该意味着当你将值类型传递给参数时会发生装箱Enum flag,对吧?

sup*_*cat 8

值得注意的是,可以在大约30行代码中编写HasFlag<T>(T thing, T flags)Enum.HasFlag扩展方法快约30倍的通用.它甚至可以成为一种扩展方法.不幸的是,在C#中不可能将这样的方法限制为仅采用枚举类型的东西; 因此,即使对于不适用的类型,Intellisense也会弹出该方法.我认为如果使用除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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 缓存“方法”将使其速度更快。 (2认同)
  • 您可以在 [C# 7.3](/sf/answers/566075191/) 中应用 System.Enum 限制! (2认同)
  • @supercat:尽管是免费分配的,但在我的测试中,您的解决方案比本机“HasFlag”慢两倍(在单元测试中循环调用每 10,000,000 次)。难道打电话给代表的费用比打拳击的费用更划算吗?或者我的单元测试有误导性? (2认同)

Ada*_*rth 7

在这种情况下,在进入HasFlags方法之前需要两次装箱调用.一种是将值类型的方法调用解析为基类型方法,另一种是将值类型作为引用类型参数传递.如果你这样做var type = 1.GetType();,你可以在IL中看到相同的内容,intGetType()调用之前将文字1装箱.方法调用上的装箱似乎只是在值类型定义本身没有覆盖方法时,可以在这里读取更多:在值类型上调用方法会导致在.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
Run Code Online (Sandbox Code Playgroud)

表示值类型时可以看到相同的情况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
Run Code Online (Sandbox Code Playgroud)

  • @sblom错误信息**仅**在暗示从“Enum”或“ValueType”继承意味着在“Enum”或“ValueType”中表示这些值类型不应该引起装箱的人中......简单地说,它确实如此。同样的论点也适用于“object”。答案确实非常简单:它是方框,因为您将值类型表示为引用类型。 (2认同)