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