Общий метод расширения, чтобы увидеть, содержит ли перечисление флаг
Учитывая это:
[Flags]
public enum MyEnum {
One = 1,
Two = 2,
Four = 4,
Eight = 8
}
public static class FlagsHelper
{
public static bool Contains(this MyEnum keys, MyEnum flag)
{
return (keys & flag) != 0;
}
}
Можно ли написать общую версию Contains, которая будет работать для любого enum
, а не только MyEnum
?
Edit:
Это будет моя версия после прочтения ваших ответов:
public static bool Contains(this Enum keys, Enum flag)
{
ulong keysVal = Convert.ToUInt64(keys);
ulong flagVal = Convert.ToUInt64(flag);
return (keysVal & flagVal) == flagVal;
}
Только что понятая - это плохая идея проверить, как я проверял (return (keys & flag) != 0;
), потому что параметр flag
может быть фактически несколькими флагами, а здравый смысл - вернуть true, только если keys
содержит все из них. Кроме того, я не буду проверять нулевые значения или даже убедиться, что они одного типа. Я мог бы использовать разные типы.
Ответы
Ответ 1
Я основывал этот метод на связке поисковых запросов SO и Google, а также с помощью рефлектора, чтобы увидеть, что MS сделала для метода .NET 4 HasFlags.
public static class EnumExt
{
/// <summary>
/// Check to see if a flags enumeration has a specific flag set.
/// </summary>
/// <param name="variable">Flags enumeration to check</param>
/// <param name="value">Flag to check for</param>
/// <returns></returns>
public static bool HasFlag(this Enum variable, Enum value)
{
if (variable == null)
return false;
if (value == null)
throw new ArgumentNullException("value");
// Not as good as the .NET 4 version of this function, but should be good enough
if (!Enum.IsDefined(variable.GetType(), value))
{
throw new ArgumentException(string.Format(
"Enumeration type mismatch. The flag is of type '{0}', was expecting '{1}'.",
value.GetType(), variable.GetType()));
}
ulong num = Convert.ToUInt64(value);
return ((Convert.ToUInt64(variable) & num) == num);
}
}
Примечания:
- Это обрабатывает nulls
- Проверяет ли тип
- Преобразует в ulong и может обрабатывать любое положительное значение перечисления. Microsoft предостерегает от использования индексов негативных флагов:
Соблюдайте осторожность, если вы определяете отрицательное число как перечислимую константу флага, поскольку многие позиции флага могут быть установлены в 1, что может привести к запутыванию вашего кода и поощрению ошибок кодирования.
Ответ 2
Не уверен, что вы используете .NET 4.0 или нет, но он поставляется со статическим методом Enum.HasFlags()
.
- Код удален (уже принятое решение уже есть) -
Ответ 3
Это мой подход, это безопасный тип и не делает никакого бокса или распаковки. Он генерирует исключение, если тип не является перечислением.
Существует метод, который вы можете использовать, если хотите превратить его в открытый статический метод, который будет набран для Enum, но тогда он не может быть методом расширения.
Также нет необходимости проверять значение null, так как struct contraint также блокирует нулевое перечисление. Я не думаю, что для улучшения этого кода нужно многое сделать, за исключением, возможно, написания его в F # или С++/CLI, чтобы вы могли наложить ограничение enum на него.
Идея состоит в том, чтобы построить функцию, используя деревья выражений, которые преобразуют перечисление в длинное, если его ничего, кроме перечисления на основе ulong, или ulong, а затем и их, по существу производя::
return value & flag == flag
public static class EnumExtensions
{
#region Public Static Methods
/// <summary>
/// Determines whether the specified value has flags. Note this method is up to 60 times faster
/// than the one that comes with .NET 4 as it avoids any explict boxing or unboxing.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param>
/// <param name="flag">The flag.</param>
/// <returns>
/// <c>true</c> if the specified value has flags; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentException">If TEnum is not an enum.</exception>
public static bool HasFlags<TEnum>(this TEnum value, TEnum flag) where TEnum:struct,IComparable,IConvertible,IFormattable
{
return EnumExtensionsInternal<TEnum>.HasFlagsDelegate(value, flag);
}
#endregion Public Static Methods
#region Nested Classes
static class EnumExtensionsInternal<TEnum> where TEnum : struct,IComparable, IConvertible, IFormattable
{
#region Public Static Variables
/// <summary>
/// The delegate which determines if a flag is set.
/// </summary>
public static readonly Func<TEnum, TEnum, bool> HasFlagsDelegate = CreateHasFlagDelegate();
#endregion Public Static Variables
#region Private Static Methods
/// <summary>
/// Creates the has flag delegate.
/// </summary>
/// <returns></returns>
private static Func<TEnum, TEnum, bool> CreateHasFlagDelegate()
{
if(!typeof(TEnum).IsEnum)
{
throw new ArgumentException(string.Format("{0} is not an Enum", typeof(TEnum)), typeof(EnumExtensionsInternal<>).GetGenericArguments()[0].Name);
}
ParameterExpression valueExpression = Expression.Parameter(typeof(TEnum));
ParameterExpression flagExpression = Expression.Parameter(typeof(TEnum));
ParameterExpression flagValueVariable = Expression.Variable(Type.GetTypeCode(typeof(TEnum)) == TypeCode.UInt64 ? typeof(ulong) : typeof(long));
Expression<Func<TEnum, TEnum, bool>> lambdaExpression = Expression.Lambda<Func<TEnum, TEnum, bool>>(
Expression.Block(
new[] { flagValueVariable },
Expression.Assign(
flagValueVariable,
Expression.Convert(
flagExpression,
flagValueVariable.Type
)
),
Expression.Equal(
Expression.And(
Expression.Convert(
valueExpression,
flagValueVariable.Type
),
flagValueVariable
),
flagValueVariable
)
),
valueExpression,
flagExpression
);
return lambdaExpression.Compile();
}
#endregion Private Static Methods
}
#endregion Nested Classes
}
Поскольку я забыл, что дерево выражений выше, это .NET 4, только следующий метод должен работать в .NET 3.5 для создания того же дерева выражений::
private static Func<TEnum, TEnum, bool> CreateHasFlagDelegate2()
{
if(!typeof(TEnum).IsEnum)
{
throw new ArgumentException(string.Format("{0} is not an Enum", typeof(TEnum)), typeof(EnumExtensionsInternal<>).GetGenericArguments()[0].Name);
}
ParameterExpression valueExpression = Expression.Parameter(
typeof(TEnum),
typeof(TEnum).Name
);
ParameterExpression flagExpression = Expression.Parameter(
typeof(TEnum),
typeof(TEnum).Name
);
var targetType = Type.GetTypeCode(typeof(TEnum)) == TypeCode.UInt64 ? typeof(ulong) : typeof(long);
Expression<Func<TEnum, TEnum, bool>> lambdaExpression = Expression.Lambda<Func<TEnum, TEnum, bool>>(
Expression.Equal(
Expression.And(
Expression.Convert(
valueExpression,
targetType
),
Expression.Convert(
flagExpression,
targetType
)
),
Expression.Convert(
flagExpression,
targetType
)
),
valueExpression,
flagExpression
);
return lambdaExpression.Compile();
}
эта версия должна компилироваться в .NET 3.5, и если это не так, я не могу понять, почему.
Ответ 4
К сожалению нет хорошего способа сделать такой метод расширения. Для этого вам нужно будет иметь общий метод, который работает с значениями enum
. К сожалению, нет способа ограничить общие аргументы как enum
// Ilegal
public static bool Contains<T>(this T value, T flag) where T : enum {
...
}
Лучшее, что я придумал, это следующее
public static bool HasFlag<T>(this System.Enum e, T flag)
{
var intValue = (int)(object)e;
var intFlag = (int)(object)flag;
return (intValue & intFlag) != 0;
}
Однако он ограничен несколькими способами
- Не тип безопасно, потому что нет требования, чтобы значение и флаг имели один и тот же тип
- Предполагается, что все значения
enum
основаны на int
.
- Приводит к возникновению бокса для простой проверки бит.
- Будут выбрасываться, если
e
есть null
Ответ 5
В основном вы можете использовать существующий метод расширения, вместо MyEnum
используйте тип Enum
. Проблема тогда в том, что он не знает, что перечисления являются флагами и не позволяют использовать оператор &
, поэтому вам просто нужно преобразовать значения перечисления в числа.
public static bool Contains(this Enum keys, Enum flag)
{
if (keys.GetType() != flag.GetType())
throw new ArgumentException("Type Mismatch");
return (Convert.ToUInt64(keys) & Convert.ToUInt64(flag)) != 0;
}
И unit test для хорошей меры:
[TestMethod]
public void TestContains()
{
var e1 = MyEnum.One | MyEnum.Two;
Assert.IsTrue( e1.Contains(MyEnum.Two) );
var e2 = MyEnum.One | MyEnum.Four;
Assert.IsFalse(e2.Contains(MyEnum.Two));
}
Ответ 6
Это пример того, что должно работать.
public static bool IsValid<T>(this T value)
{
return Enum.IsDefined(value.GetType(), value);
}
Ответ 7
У меня есть другой подход, который я только что приготовил быстро, используя тот факт, что Delegate.CreateDelegate позволяет преобразовать методы для Enum и их базовые типы. Следующий подход во многом похож на мой предыдущий ответ, но я чувствую, что его легче читать для людей, которые не знают синтаксис дерева выражений. В основном мы знаем, что Enums имеет только 8 возможных базовых типов, поэтому мы просто создаем статический метод для каждого вызова, который он может использовать. Поскольку я собираюсь для краткости, я использую анонимные методы, которые назывались такими же, как возможные значения typecode. Этот подход будет работать в .Net 3.5::
public static class EnumHelper
{
delegate bool HasFlag<T>(T left,T right);
static readonly HasFlag<Byte> Byte = (x,y)=> (x&y) ==y;
static readonly HasFlag<SByte> Sbyte = (x,y)=> (x&y) ==y;
static readonly HasFlag<Int16> Int16 = (x,y)=> (x&y) ==y;
static readonly HasFlag<UInt16> UInt16 = (x,y)=> (x&y) ==y;
static readonly HasFlag<Int32> Int32 = (x,y)=> (x&y) ==y;
static readonly HasFlag<UInt32> UInt32 = (x,y)=> (x&y) ==y;
static readonly HasFlag<Int64> Int64 = (x,y)=> (x&y) ==y;
static readonly HasFlag<UInt64> UInt64 = (x,y)=> (x&y) ==y;
public static bool HasFlags<TEnum>(this TEnum @enum,TEnum flag) where TEnum:struct,IConvertible,IComparable,IFormattable
{
return Enum<TEnum>.HasFlag(@enum,flag);
}
class Enum<TEnum> where TEnum:struct,IConvertible,IComparable,IFormattable
{
public static HasFlag<TEnum> HasFlag = CreateDelegate();
static HasFlag<TEnum> CreateDelegate()
{
if (!typeof(TEnum).IsEnum) throw new ArgumentException(string.Format("{0} is not an enum", typeof(TEnum)), typeof(Enum<>).GetGenericArguments()[0].Name);
var delegateName = Type.GetTypeCode(typeof(TEnum)).ToString();
var @delegate = typeof(EnumHelper).GetField(delegateName,BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as Delegate;
return Delegate.CreateDelegate(typeof(HasFlag<TEnum>), @delegate.Method) as HasFlag<TEnum>;
}
}
}
Ответ 8
Другой способ реализации HasFlag для .NET Framework 3.5.
public static bool HasFlag(this Enum e, Enum flag)
{
// Check whether the flag was given
if (flag == null)
{
throw new ArgumentNullException("flag");
}
// Compare the types of both enumerations
if (e.GetType() != (flag.GetType()))
{
throw new ArgumentException(string.Format(
"The type of the given flag is not of type {0}", e.GetType()),
"flag");
}
// Get the type code of the enumeration
var typeCode = e.GetTypeCode();
// If the underlying type of the flag is signed
if (typeCode == TypeCode.SByte || typeCode == TypeCode.Int16 || typeCode == TypeCode.Int32 ||
typeCode == TypeCode.Int64)
{
return (Convert.ToInt64(e) & Convert.ToInt64(flag)) != 0;
}
// If the underlying type of the flag is unsigned
if (typeCode == TypeCode.Byte || typeCode == TypeCode.UInt16 || typeCode == TypeCode.UInt32 ||
typeCode == TypeCode.UInt64)
{
return (Convert.ToUInt64(e) & Convert.ToUInt64(flag)) != 0;
}
// Unsupported flag type
throw new Exception(string.Format("The comparison of the type {0} is not implemented.", e.GetType().Name));
}
Этот метод расширения поддерживает все возможные типы для перечисления (byte
, sbyte
, short
, ushort
, int
, uint
, long
и ulong
). В основном, метод проверяет, подписано ли заданное перечисление /unsigned и преобразует флаг в тип с наивысшим размером поддерживаемых типов для перечисления. Затем простое сравнение выполняется с помощью оператора &
.
Как объясняется в других сообщениях, мы не можем определить ограничение общего типа с перечислением, и нет смысла использовать generic с ограничением struct
, потому что разработчики w могли бы вставлять другие типы или структуры перечислений. Поэтому я считаю, что лучше не использовать для этого общий метод.