Почему Enum HasFlag метод нужен бокс?

Я читаю "С# через 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, правильно?

Ответ 1

В этом случае требуется два вызова бокса, прежде чем вы попадете в метод HasFlags. Один из них заключается в разрешении вызова метода на тип значения методу базового типа, а другой передает тип значения в качестве параметра ссылочного типа. Вы можете видеть то же самое в IL, если вы делаете var type = 1.GetType();, буква int 1 помещается в бокс перед вызовом GetType(). Вызов метода бокса по методу, кажется, только в том случае, если методы не переопределены в самом определении типа значения, больше можно прочитать здесь: Вызывает ли вызов метод по типу значения в боксе. NET?суб >

HasFlags принимает аргумент Enum class, поэтому здесь будет происходить бокс. Вы пытаетесь передать то, что является тип значения в нечто ожидающее ссылочного типа. Чтобы представить значения в качестве ссылок, происходит бокс.

Существует много поддержки компилятора для типов значений и их наследования (с 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

Ответ 2

Стоит отметить, что общий HasFlag<T>(T thing, T flags), который примерно в 30 раз быстрее, чем метод расширения Enum.HasFlag, может быть написан примерно в 30 строках кода. Его можно даже превратить в метод расширения. К сожалению, в С# невозможно ограничить такой метод только тем, что нужно перечислять перечисленным типам; следовательно, Intellisense выдает метод даже для типов, для которых он неприменим. Я думаю, что если бы кто-то использовал какой-то другой язык, кроме С# или 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);
    }
}

Ответ 3

Enum наследует от ValueType, который является... классом! Следовательно, бокс.

Обратите внимание, что класс Enum может представлять любое перечисление, независимо от его базового типа, в виде значения в коробке. В то время как значение, такое как FileAttributes.Hidden, будет представлено как тип реального значения, int.

Изменить: пусть здесь различают тип и представление. int представляется в памяти как 32 бита. Его тип происходит от ValueType. Как только вы назначаете int классу object или производного класса (ValueType, класс Enum), вы боксируете его, эффективно меняя свое представление на класс, содержащий теперь 32 бита, плюс дополнительные информацию о классе.

Ответ 4

В этом вызове задействованы две операции по боксу, а не только одна. И оба требуются по одной простой причине: Enum.HasFlag() нужна информация о типе, а не только значения, для this и flag.

В большинстве случаев значение enum действительно представляет собой всего лишь набор бит, и компилятор имеет всю необходимую информацию о типе из типов enum, представленных в сигнатуре метода.

Однако в случае Enum.HasFlags() самое первое, что он делает, это вызов this.GetType() и flag.GetType() и убедитесь, что они идентичны. Если вам нужна версия без имени, вы бы попросили if ((attribute & flag) != 0) вместо вызова Enum.HasFlags().

Ответ 5

Когда вы передаете тип значения метода, который принимает объект как параметр, как в случае с console.writeline, будет существовать операция по боксу. Джеффри Рихтер подробно обсуждает это в той же книге, которую вы упомянули.

В этом случае вы используете метод string.format для console.writeline и берете массив params объекта []. Итак, ваш bool, будет брошен в объект, поэтому вы получите боксерскую операцию. Вы можете избежать этого, вызвав .ToString() в bool.

Ответ 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 (исходный код был загружен из sourceource):

    [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);