Enum.HasFlag,为什么没有Enum.SetFlag?

bev*_*qua 31 c# enums flags

我必须为我声明的每个标志类型构建一个扩展方法,如下所示:

public static EventMessageScope SetFlag(this EventMessageScope flags, 
    EventMessageScope flag, bool value)
{
    if (value)
        flags |= flag;
    else
        flags &= ~flag;

    return flags;
}
Run Code Online (Sandbox Code Playgroud)

为什么不存在一个Enum.SetFlagEnum.HasFlag

另外,为什么这总不起作用?

public static bool Get(this EventMessageScope flags, EventMessageScope flag)
{
    return ((flags & flag) != 0);
}
Run Code Online (Sandbox Code Playgroud)

例如,如果我有:

var flag = EventMessageScope.Private;
Run Code Online (Sandbox Code Playgroud)

并检查它:

if(flag.Get(EventMessageScope.Public))
Run Code Online (Sandbox Code Playgroud)

EventMessageScope.Public真的是EventMessageScope.Private | EventMessageScope.PublicOnly,它返回true.

当它不是,因为Private不公开,它只是一半公开.

同样适用于:

if(flag.Get(EventMessageScope.None))

哪个返回false,除了范围实际上是None(0x0),它应该总是返回true?

MiF*_*vil 41

为什么没有Enum.SetFlag就像有一个Enum.HasFlag?

HasFlag 因为按位操作需要更复杂的逻辑并重复相同的标志两次

 myFlagsVariable=    ((myFlagsVariable & MyFlagsEnum.MyFlag) ==MyFlagsEnum.MyFlag );
Run Code Online (Sandbox Code Playgroud)

所以MS决定实施它.

SetFlag和ClearFlag在C#中简洁明了

    flags |= flag;// SetFlag

    flags &= ~flag; // ClearFlag 
Run Code Online (Sandbox Code Playgroud)

但不幸的是不直观.每当我需要设置(或清除)一个标志时,我会花几秒钟(或几分钟)来思考:该方法的名称是什么?为什么它没有在intellisense中显示?或者不,我必须使用按位运算.注意,有些开发人员也会问:什么是按位操作?

是否应创建SetFlag和ClearFlag扩展名 - YES显示在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")
Run Code Online (Sandbox Code Playgroud)

并且应该向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  ")
Run Code Online (Sandbox Code Playgroud)

  • 请注意,您所称的"二进制操作"实际上是"**按位操作**".二元运算是另一个概念,它只意味着有两个操作数(`+`也是一个二元运算符).还有一元运算符(例如```)和三元运算符(到目前为止只有一个,即`?:`,如'a?b:c`).`&`和`|`位运算符都是二元运算符(需要两个操作数),而`~`是一元运算符(它只是反转其操作数的位). (3认同)

sma*_*man 10

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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Eri*_*let 9

我做了一些适合我的事情,这很简单......

    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;
    }
Run Code Online (Sandbox Code Playgroud)

用法:

    var fa = FileAttributes.Normal;
    fa = fa.SetFlag(FileAttributes.Hidden, true);
Run Code Online (Sandbox Code Playgroud)


Jam*_*oux 5

现在是 2021 年,C# 有很多不错的功能,这意味着应该有一种更优雅的方式来实现这一点。让我们讨论一下之前答案的主张......

声明 1:关闭标志效率低下,因为它使用两个操作并且调用另一个方法只会增加更多开销。

这应该是错误的。如果添加 AggressiveInlined 编译器标志,编译器应该将按位操作提升为直接内联操作。如果您正在编写关键代码,您可能需要对其进行基准测试以确认,因为即使在次要编译器版本之间,结果也可能有所不同。但关键是,您应该能够调用便捷方法,而无需支付方法查找成本。

声明 2:它过于冗长,因为您必须设置标志,然后分配返回值。

这也应该是错误的。C# 提供“ref”,它允许您通过引用直接操作值类型参数(在本例中为枚举)。与 AggressiveInlined 相结合,编译器应该足够智能,可以完全删除引用指针,并且生成的 IL 应该看起来与直接内联两个按位运算一样。

注意事项:当然,这都是理论。也许其他人可以在此处的评论中加入并检查下面建议的代码中的 IL。我自己没有足够的经验来观察 IL(现在也没有时间)来看看假设的主张是否属实。但我认为这个答案仍然值得发布,因为事实是 C#应该能够完成我所解释的事情。

如果其他人可以确认这一点,我可以相应地更新答案。

public enum MyCustomEnum : long
{
    NO_FLAGS            = 0,
    SOME_FLAG           = 1,
    OTHER_FLAG          = 1 << 1,
    YET_ANOTHER_FLAG    = 1 << 2,
    ANOTHER STILL       = 1 << 3
}

public static class MyCustomEnumExt
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void TurnOFF(ref this MyCustomEnum status, MyCustomEnum flag)
        => status &= ~flag;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void TurnON(ref this MyCustomEnum status, MyCustomEnum flag)
        => status |= flag;
}
Run Code Online (Sandbox Code Playgroud)

您应该能够使用如下代码:

//Notice you don't have to return a value from the extension methods to assign manually.
MyCustomEnum mc = MyCustomEnum.SOME_FLAG;
mc.TurnOFF(MyCustomEnum.SOME_FLAG);
mc.TurnON(MyCustomEnum.OTHER_FLAG);
Run Code Online (Sandbox Code Playgroud)

即使编译器无法正确优化它,它仍然非常方便。至少您可以在非关键代码中使用它并期望具有出色的可读性。


Mar*_*ade 3

操作&员会给您提供与 相同的答案,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

您的方法将始终返回falsefor,(EventMessageScope.None).Get(EventMessaageScope.None)因为None == 0只有当 AND 运算的结果为零时,它才会返回 true。0 & 0 == 0