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 является типом значений, вы не можете передать его и установить его тип. Для всех вас, которые считают его глупым, я говорю это вам: не все разработчики понимают битовые флаги и как включать или отключать их (что гораздо менее интуитивно понятно).

Не глупая идея, просто невозможно.