Почему метод Hasflag Enum нуждается в боксе?

Я читаю "C# via CLR", и на странице 380 есть заметка, говорящая следующее:

Примечание класс перечисления определяет метод HasFlag, определенный следующим образом

public Boolean HasFlag(Enum flag);

используя этот метод, вы можете переписать вызов на консоль.Напиши вот так:

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

однако я рекомендую вам избегать метода HasFlag по этой причине:

так как требуется параметр из типа Enum любое значение, которое вы передаете ему, должно быть в коробке, требующей выделения памяти ."

Я не могу понять это смелое утверждение -- почему"

любое значение, которое вы передаете ему, должно быть в коробке

на flag тип параметра:Enum, который является типом значения, почему должен быть бокс? "Любое значение, которое вы передаете ему, должно быть в коробке" должно означать, что бокс происходит, когда вы передаете тип значения параметру Enum flag, да?

6 ответов


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

на HasFlags принимает Enum класс


стоит отметить, что универсального HasFlag<T>(T thing, T flags), который примерно в 30 раз быстрее, чем 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);
    }
}

Enum наследует от ValueType, который является... класс! Отсюда и бокс.

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

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


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

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


в этом вызове участвуют две боксерские операции, а не только одна. И оба необходимы по одной простой причине: 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);