Enum.HasFlag, почему нет Enum.SetFlag? - PullRequest
27 голосов
/ 01 мая 2011

Мне нужно создать метод расширения для каждого типа флага, который я объявляю, вот так:

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, он возвращает истину.

Когда это не так, потому что Private не является публичным, это просто наполовину публично.

То же самое относится к:

if(flag.Get(EventMessageScope.None))

Какойвозвращает false, за исключением того, что область действия на самом деле None (0x0), когда она всегда должна возвращать true?

Ответы [ 8 ]

33 голосов
/ 07 июля 2012

Почему нет Enum.SetFlag, как есть Enum.HasFlag?

HasFlag в качестве побитовой операции требовалась более сложная логика и повторение одного и того же флага дважды

 myFlagsVariable=    ((myFlagsVariable & MyFlagsEnum.MyFlag) ==MyFlagsEnum.MyFlag );

поэтому MS решила это реализовать.

SetFlag и ClearFlag лаконичны в C #

    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  ")
10 голосов
/ 02 мая 2011
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);
            }
        }
    }
}
8 голосов
/ 05 февраля 2014

Я сделал что-то, что работает для меня, и это очень просто ...

    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);
3 голосов
/ 02 мая 2011

Оператор & даст вам тот же ответ с a & b, что и с b & a, поэтому

(EventMessaageScope.Private) .Get (EventMessageScope.PrivateEventMessageScope.PublicOnly)

- это то же самое, что и запись

(EventMessageScope.Private | EventMessageScope.PublicOnly) .Get (EventMessaageScope.Private)

Если вы просто хотите узнать, является ли значение таким же , как EventMessaageScope.Public, тогда просто используйте равно :

EventMessageScope.Private == EventMessageScope.Public

Ваш метод всегда будет возвращать false для (EventMessageScope.None).Get(EventMessaageScope.None), поскольку None == 0 и возвращается только когдарезультат операции AND равен , а не ноль.0 & 0 == 0.

1 голос
/ 03 апреля 2017

Вот еще один быстрый и грязный способ 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)flagsInt;
    }
1 голос
/ 02 мая 2011

Перечисления были испорчены языком Си давным-давно.Для разработчиков было важно иметь хоть немного безопасности типов в языке C #, поэтому Enum.SetFlags не оставлял места, когда базовый тип мог быть чем-то между байтом и длинным.Между прочим, еще одна проблема, вызванная Си.

Правильный способ решения этой проблемы заключается в явном написании такого кода внутри строки, а , а не , попытайтесь вставить его в метод расширения.Вы не хотите писать макрос C на языке C #.

1 голос
/ 02 мая 2011

Чтобы ответить на часть вашего вопроса: функция Get работает в соответствии с бинарной логикой - она ​​проверяет наличие совпадений.Если вы хотите сопоставить весь набор флагов, рассмотрите это вместо этого:

return ((flags & flag) != flag);

Относительно "почему там нет SetFlag" ... вероятно, потому что это действительно не нужно.Флаги являются целыми числами.Уже существует соглашение для работы с ними, и оно применяется также и к флагам.Если вы не хотите писать это с | и & - для этого предназначены специальные статические дополнения - вы можете просто использовать свои собственные функции, как вы продемонстрировали:)

0 голосов
/ 03 июня 2012

Причина, по которой я нахожу, состоит в том, что, поскольку enum является типом значения, вы не можете передать его и установить его тип.Всем вам, кто считает это глупым, я говорю вам следующее: не все разработчики понимают битовые флаги и как их включать или выключать (что гораздо менее интуитивно понятно).возможно.

...