Почему метод 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);