Сравнение флажков перечисления в С#
Мне нужно определить, установлен ли флаг в значение enum, тип которого помечен атрибутом Flag.
Обычно это делается так:
(value & flag) == flag
Но так как мне нужно сделать это с помощью общего (иногда во время выполнения я событие имеет только ссылку "Enum". Я не могу найти простой способ использовать оператор и. В настоящий момент я делаю это так:
public static bool IsSet<T>(this T value, T flags) where T : Enum
{
Type numberType = Enum.GetUnderlyingType(typeof(T));
if (numberType.Equals(typeof(int)))
{
return BoxUnbox<int>(value, flags, (a, b) => (a & b) == b);
}
else if (numberType.Equals(typeof(sbyte)))
{
return BoxUnbox<sbyte>(value, flags, (a, b) => (a & b) == b);
}
else if (numberType.Equals(typeof(byte)))
{
return BoxUnbox<byte>(value, flags, (a, b) => (a & b) == b);
}
else if (numberType.Equals(typeof(short)))
{
return BoxUnbox<short>(value, flags, (a, b) => (a & b) == b);
}
else if (numberType.Equals(typeof(ushort)))
{
return BoxUnbox<ushort>(value, flags, (a, b) => (a & b) == b);
}
else if (numberType.Equals(typeof(uint)))
{
return BoxUnbox<uint>(value, flags, (a, b) => (a & b) == b);
}
else if (numberType.Equals(typeof(long)))
{
return BoxUnbox<long>(value, flags, (a, b) => (a & b) == b);
}
else if (numberType.Equals(typeof(ulong)))
{
return BoxUnbox<ulong>(value, flags, (a, b) => (a & b) == b);
}
else if (numberType.Equals(typeof(char)))
{
return BoxUnbox<char>(value, flags, (a, b) => (a & b) == b);
}
else
{
throw new ArgumentException("Unknown enum underlying type " + numberType.Name + ".");
}
}
private static bool BoxUnbox<T>(object value, object flags, Func<T, T, bool> op)
{
return op((T)value, (T)flags);
}
Но мне не нравятся бесконечные блоки if-else, поэтому есть способ применить эти значения, чтобы я мог использовать оператор and или любое другое решение, чтобы проверить это?
Ответы
Ответ 1
Лично я считаю, что это выглядит хорошо, потому что вы завернули его в единую целевую функцию. Если бы у вас был этот код, разбросанный по всей программе, я думаю, что у вас возникнут проблемы, но то, что вы создали, улучшает ясность во всем мире, и сама функция достаточно ясна, что она делает.
Просто мое мнение, конечно.
Вы могли бы использовать ключевое слово is, которое могло бы помочь немного
public static bool IsSet<T>(this T value, T flags) where T : Enum
{
if (value is int)
{
return ((int)(object)a & (int)(object)b) == (int)(object)b);
}
//etc...
Ответ 2
Для меня это выглядит сложнее. Как об этом (имея в виду, что перечисление всегда отображается на целочисленный тип значения):
public static bool IsSet<T>(T value, T flags) where T : struct
{
// You can add enum type checking to be perfectly sure that T is enum, this have some cost however
// if (!typeof(T).IsEnum)
// throw new ArgumentException();
long longFlags = Convert.ToInt64(flags);
return (Convert.ToInt64(value) & longFlags) == longFlags;
}
Ответ 3
Я написал набор методов расширения для перечислений, если вам это нужно:
public static class EnumExtensions
{
private static void CheckEnumWithFlags<T>()
{
if (!typeof(T).IsEnum)
throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
if (!Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
}
public static bool IsFlagSet<T>(this T value, T flag) where T : struct
{
CheckEnumWithFlags<T>();
long lValue = Convert.ToInt64(value);
long lFlag = Convert.ToInt64(flag);
return (lValue & lFlag) != 0;
}
public static IEnumerable<T> GetFlags<T>(this T value) where T : struct
{
CheckEnumWithFlags<T>();
foreach (T flag in Enum.GetValues(typeof(T)).Cast<T>())
{
if (value.IsFlagSet(flag))
yield return flag;
}
}
public static T SetFlags<T>(this T value, T flags, bool on) where T : struct
{
CheckEnumWithFlags<T>();
long lValue = Convert.ToInt64(value);
long lFlag = Convert.ToInt64(flags);
if (on)
{
lValue |= lFlag;
}
else
{
lValue &= (~lFlag);
}
return (T)Enum.ToObject(typeof(T), lValue);
}
public static T SetFlags<T>(this T value, T flags) where T : struct
{
return value.SetFlags(flags, true);
}
public static T ClearFlags<T>(this T value, T flags) where T : struct
{
return value.SetFlags(flags, false);
}
public static T CombineFlags<T>(this IEnumerable<T> flags) where T : struct
{
CheckEnumWithFlags<T>();
long lValue = 0;
foreach (T flag in flags)
{
long lFlag = Convert.ToInt64(flag);
lValue |= lFlag;
}
return (T)Enum.ToObject(typeof(T), lValue);
}
}
Основной недостаток заключается в том, что вы не можете указать where T : Enum
: он явно запрещен ( "Constraint не может быть специальным классом" System.Enum "), поэтому методы расширения появятся в intellisense для всех структур... Я добавил метод CheckEnumWithFlags
, чтобы проверить, что тип на самом деле является перечислением, и имеет атрибут Flags
.
UPDATE: Недавно Джон Скит начал интересную библиотеку под названием UnconstrainedMelody, которая делает точно такие же вещи и работает с ограничением ограничения общего типа упомянутый выше
Ответ 4
Это должно выполняться для типов enum
с любыми базовыми типами:
public static bool IsSet<T>(this T value, T flags) where T : struct
{
return (Convert.ToInt64(value) & Convert.ToInt64(flags)) ==
Convert.ToInt64(flags);
}
Convert.ToInt64
используется, потому что 64-битное целое является "самым широким" интегральным типом, доступным для всех значений перечисления (даже ulong
). Обратите внимание, что char
не является допустимым базовым типом. Похоже, что он недействителен в С#, но он вообще действует в CIL/для CLR.
Кроме того, вы не можете принудительно применять ограничение общего типа для перечислений (т.е. where T : struct
); самое лучшее, что вы можете сделать, это использовать where T : struct
для принудительного применения T
как типа значения, а затем выполнить динамическую проверку, чтобы гарантировать, что T
- тип перечисления.
Для полноты, вот мой очень короткий тестовый жгут:
static class Program
{
static void Main(string[] args)
{
Debug.Assert(Foo.abc.IsSet(Foo.abc));
Debug.Assert(Bar.def.IsSet(Bar.def));
Debug.Assert(Baz.ghi.IsSet(Baz.ghi));
}
enum Foo : int
{
abc = 1,
def = 10,
ghi = 100
}
enum Bar : sbyte
{
abc = 1,
def = 10,
ghi = 100
}
enum Baz : ulong
{
abc = 1,
def = 10,
ghi = 100
}
}
Ответ 5
Просто используйте метод Enum.HasFlag()!
Ответ 6
Я использовал это для сравнения флагов
public static bool IsSet<T>(this T input, T match)
{
return (Convert.ToUInt32(input) & Convert.ToUInt32(match)) != 0;
}
Здесь вы можете выполнять различные преобразования. От int до короткого и длинного.
Ответ 7
или... public static bool IsSet (это значение Enum, Enum compare) { int baseValue = значение .ToInt32(); int compareValue = compare.ToInt32(); if (baseValue == 0) return false; return ((baseValue и compareValue) == compareValue); }