Enum.HasFlag, почему нет Enum.SetFlag?
Мне нужно создать метод расширения для каждого объявляемого типа флага, например:
public static EventMessageScope SetFlag(this EventMessageScope flags,
EventMessageScope flag, bool value)
{
if (value)
flags |= flag;
else
flags &= ~flag;
return flags;
}
Почему нет Enum.SetFlag
, например, Enum.HasFlag
?
Кроме того, почему это не работает всегда?
public static bool Get(this EventMessageScope flags, EventMessageScope flag)
{
return ((flags & flag) != 0);
}
Например, если у меня есть:
var flag = EventMessageScope.Private;
И проверьте это как:
if(flag.Get(EventMessageScope.Public))
Где EventMessageScope.Public
действительно есть EventMessageScope.Private | EventMessageScope.PublicOnly
, он возвращает true.
Когда это не так, потому что Private
не является общедоступным, он просто наполовину публичный.
То же самое для:
if(flag.Get(EventMessageScope.None))
Что возвращает false
, за исключением того, что область действия действительно None
(0x0
), когда она всегда должна возвращать true?
Ответы
Ответ 1
Оператор &
даст вам тот же ответ с a & b
, как это будет с b & a
, поэтому
(EventMessaageScope.Private).Get(EventMessageScope.Private | EventMessageScope.PublicOnly)
совпадает с записью
(EventMessageScope.Private | EventMessageScope.PublicOnly).Get(EventMessaageScope.Private)
Если вы просто хотите узнать, совпадает ли значение с EventMessaageScope.Public, просто используйте equals:
EventMessageScope.Private == EventMessageScope.Public
Ваш метод всегда будет возвращать false
для (EventMessageScope.None).Get(EventMessaageScope.None)
, потому что None == 0
и он возвращает true только тогда, когда результат операции И не равен нулю. 0 & 0 == 0
.
Ответ 2
Почему нет Enum.SetFlag, как есть Enum.HasFlag?
HasFlag
как побитовая операция требует более сложной логики и повторения одного и того же знака дважды
myFlagsVariable= ((myFlagsVariable & MyFlagsEnum.MyFlag) ==MyFlagsEnum.MyFlag );
поэтому MS решила его реализовать.
SetFlag и ClearFlag являются краткими в С#
flags |= flag;// SetFlag
flags &= ~flag; // ClearFlag
но, к сожалению, не интуитивно. Каждый раз, когда мне нужно установить (или очистить) флаг, я потрачу несколько секунд (или минут), чтобы подумать: как называется метод? Почему это не показано в intellisense? Или нет, я должен использовать побитовые операции. Обратите внимание, что некоторые разработчики также спросят: что такое побитовая операция?
Должны быть созданы расширения SetFlag и ClearFlag - ДА, чтобы появиться в intellisense.
Если разработчики должны использовать расширения SetFlag и ClearFlag - нет, потому что они неэффективны.
Мы создали расширения в нашем библиотечном классе EnumFlagsHelper, как в
SomeEnumHelperMethodsThatMakeDoingWhatYouWantEasier, но назвал функцию SetFlag вместо Include и ClearFlag вместо Remove.
В теле методов SetFlag (и в сводном комментарии) я решил добавить
Debug.Assert( false, " do not use the extension due to performance reason, use bitwise operation with the explanatory comment instead \n
flags |= flag;// SetFlag")
и подобное сообщение должно быть добавлено в ClearFlag
Debug.Assert( false, " do not use the extension due to performance reason, use bitwise operation with the explanatory comment instead \n
flags &= ~flag; // ClearFlag ")
Ответ 3
public static class SomeEnumHelperMethodsThatMakeDoingWhatYouWantEasier
{
public static T IncludeAll<T>(this Enum value)
{
Type type = value.GetType();
object result = value;
string[] names = Enum.GetNames(type);
foreach (var name in names)
{
((Enum) result).Include(Enum.Parse(type, name));
}
return (T) result;
//Enum.Parse(type, result.ToString());
}
/// <summary>
/// Includes an enumerated type and returns the new value
/// </summary>
public static T Include<T>(this Enum value, T append)
{
Type type = value.GetType();
//determine the values
object result = value;
var parsed = new _Value(append, type);
if (parsed.Signed is long)
{
result = Convert.ToInt64(value) | (long) parsed.Signed;
}
else if (parsed.Unsigned is ulong)
{
result = Convert.ToUInt64(value) | (ulong) parsed.Unsigned;
}
//return the final value
return (T) Enum.Parse(type, result.ToString());
}
/// <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);
}
/// <summary>
/// Removes an enumerated type and returns the new value
/// </summary>
public static T Remove<T>(this Enum value, T remove)
{
Type type = value.GetType();
//determine the values
object result = value;
var parsed = new _Value(remove, type);
if (parsed.Signed is long)
{
result = Convert.ToInt64(value) & ~(long) parsed.Signed;
}
else if (parsed.Unsigned is ulong)
{
result = Convert.ToUInt64(value) & ~(ulong) parsed.Unsigned;
}
//return the final value
return (T) Enum.Parse(type, result.ToString());
}
//class to simplfy narrowing values between
//a ulong and long since either value should
//cover any lesser value
private class _Value
{
//cached comparisons for tye to use
private static readonly Type _UInt32 = typeof (long);
private static readonly Type _UInt64 = typeof (ulong);
public readonly long? Signed;
public readonly ulong? Unsigned;
public _Value(object value, Type type)
{
//make sure it is even an enum to work with
if (!type.IsEnum)
{
throw new ArgumentException(
"Value provided is not an enumerated type!");
}
//then check for the enumerated value
Type compare = Enum.GetUnderlyingType(type);
//if this is an unsigned long then the only
//value that can hold it would be a ulong
if (compare.Equals(_UInt32) || compare.Equals(_UInt64))
{
Unsigned = Convert.ToUInt64(value);
}
//otherwise, a long should cover anything else
else
{
Signed = Convert.ToInt64(value);
}
}
}
}
Ответ 4
Я сделал то, что работает для меня и это очень просто...
public static T SetFlag<T>(this Enum value, T flag, bool set)
{
Type underlyingType = Enum.GetUnderlyingType(value.GetType());
// note: AsInt mean: math integer vs enum (not the c# int type)
dynamic valueAsInt = Convert.ChangeType(value, underlyingType);
dynamic flagAsInt = Convert.ChangeType(flag, underlyingType);
if (set)
{
valueAsInt |= flagAsInt;
}
else
{
valueAsInt &= ~flagAsInt;
}
return (T)valueAsInt;
}
Использование:
var fa = FileAttributes.Normal;
fa = fa.SetFlag(FileAttributes.Hidden, true);
Ответ 5
Чтобы ответить на часть вашего вопроса: функция Get работает правильно в соответствии с бинарной логикой - она проверяет соответствие. Если вы хотите совместить весь набор флагов, подумайте об этом:
return ((flags & flag) != flag);
Относительно "почему нет SetFlag"... возможно, потому что это действительно не нужно. Флаги являются целыми числами. Существует уже конвенция для борьбы с ними, и это относится и к флагам. Если вы не хотите писать его с помощью |
и &
- для чего предназначены пользовательские статические аддоны - вы можете просто использовать свои собственные функции, как вы показали себя:)
Ответ 6
Энумы давно увязаны языком C. Наличие модификации безопасности типов на языке С# было важно для дизайнеров, не оставляя места для Enum.SetFlags, когда базовый тип может быть чем угодно между байтом и длинным. Другая проблема, вызванная C btw.
Правильный способ справиться с этим заключается в том, чтобы написать этот тип кода в явном виде и не пытаться вставить его в метод расширения. Вы не хотите писать макрос C на языке С#.
Ответ 7
Вот еще один быстрый и грязный способ SetFlag для любого Enum:
public static T SetFlag<T>(this T flags, T flag, bool value) where T : struct, IComparable, IFormattable, IConvertible
{
int flagsInt = flags.ToInt32(NumberFormatInfo.CurrentInfo);
int flagInt = flag.ToInt32(NumberFormatInfo.CurrentInfo);
if (value)
{
flagsInt |= flagInt;
}
else
{
flagsInt &= ~flagInt;
}
return (T)(Object)val;
}
Ответ 8
Причина, по которой я нахожу, состоит в том, что поскольку enum является типом значений, вы не можете передать его и установить его тип. Для всех вас, которые считают его глупым, я говорю это вам: не все разработчики понимают битовые флаги и как включать или отключать их (что гораздо менее интуитивно понятно).
Не глупая идея, просто невозможно.