Работа с перечислениями Enum в C# имеет очень большие издержки. Это странно, т.к. перечисления целочисленны по своей природе, но из-за требований безопасности типов даже простые операции обходятся дорого. Подробно этот аспект освещён в книге Бена Уотсона “Высокопроизводительный код на платформе .NET” (2-е издание) в главе 6 “Использование среды .NET Framework” раздел “Удивительно высокие издержки использования перечислений”. Там рассмотрено внутреннее устройство метода Enum.HasFlag. Анализ же метода Enum.IsDefined оставлен в виде домашнего задания. Займёмся анализом, учтя основную рекомендацию Бена: “если окажется, что проверять наличие флага приходится часто, реализуйте проверку самостоятельно”. Посмотрим внутренее устройство Enum.IsDefined, реализуем самодельную альтернативу MyEnum.MyIsDefined и сравним их в деле.
ILSpy показывает следующее устройство метода Enum.IsDefined:
// System.Type publicvirtualboolIsEnumDefined(objectvalue) { if (value == null) { thrownew ArgumentNullException("value"); } if (!IsEnum) { thrownew ArgumentException(SR.Arg_MustBeEnum, "value"); } Type type = value.GetType(); if (type.IsEnum) { if (!type.IsEquivalentTo(this)) { thrownew ArgumentException(SR.Format(SR.Arg_EnumAndObjectMustBeSameType, type, this)); } type = type.GetEnumUnderlyingType(); } if (type == typeof(string)) { string[] enumNames = GetEnumNames(); object[] array = enumNames; if (Array.IndexOf(array, value) >= 0) { returntrue; } returnfalse; } if (IsIntegerType(type)) { Type enumUnderlyingType = GetEnumUnderlyingType(); if (enumUnderlyingType.GetTypeCodeImpl() != type.GetTypeCodeImpl()) { thrownew ArgumentException(SR.Format(SR.Arg_EnumUnderlyingTypeAndObjectMustBeSameType, type, enumUnderlyingType)); } Array enumRawConstantValues = GetEnumRawConstantValues(); return BinarySearch(enumRawConstantValues, value) >= 0; } thrownew InvalidOperationException(SR.InvalidOperation_UnknownEnumType); }
Выглядит этот код сложно, причем я не привожу ещё реализации многочисленных внутренних функций типа GetEnumUnderlyingType, GetEnumRawConstantValues и др., чтобы не загромождать повествование. Видно, что в Enum.IsDefined можно передать и численное значение int, и имя в виде строки, и аргумент в виде значения преречисления PetType.Dog - во всех случаях метод проверит есть ли такое значение в перечислении (вернёт True, если есть, либо False, если нет).
Реализуем метод Enum.IsDefined самостоятельно. Для этого преобразуем перечисление в словарь equivalentEnumDictionary внутри класса MyEnum. Тогда эквивалентом метода Enum.IsDefined будет метод MyEnum.MyIsDefined с таким же набором входных аргументов:
Преобразование перечисления в словарь произойдёт единожды при первом обращении к методу MyEnum.MyIsDefined.
Примечание: LINQ преобразование в словарь я также пробовал в виде .ToDictionary(k => (int)k, v => v.ToString()) и .ToDictionary(k => (int)k, v => Enum.GetName(enumType, (int)v)). Вариант, представленный в теле метода MyIsDefined, оказался самым шустрым.
Теперь сравним насколько самостоятельная проверка быстрее встроенной. Для этого воспользуемся библиотекой BenchmarkDotNet:
Получается, что самостоятельная проверка MyEnum.MyIsDefined более чем в два раза быстрее даже при такой прямолинейной реализации. Стоит согласиться с Беном Уотсоном, что если проверять Enum.IsDefined приходится часто, то реализация самостоятельной проверки даст существенную выгоду по быстродействию. При обычном сценарии достаточно пользоваться встроенной Enum.IsDefined.