简体   繁体   English

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

[英]Enum.HasFlag, why no Enum.SetFlag?

I have to build an extension method for each flag type I declare, like so:我必须为我声明的每个标志类型构建一个扩展方法,如下所示:

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

    return flags;
}

Why isn't there an Enum.SetFlag like there is an Enum.HasFlag ?为什么没有Enum.SetFlag就像有Enum.HasFlag一样?

Also, why does this not work always?另外,为什么这并不总是有效?

public static bool Get(this EventMessageScope flags, EventMessageScope flag)
{
    return ((flags & flag) != 0);
}

For example, if I have:例如,如果我有:

var flag = EventMessageScope.Private;

And check it like:并检查它:

if(flag.Get(EventMessageScope.Public))

Where EventMessageScope.Public really is EventMessageScope.Private | EventMessageScope.PublicOnly EventMessageScope.Public真正的地方是EventMessageScope.Private | EventMessageScope.PublicOnly EventMessageScope.Private | EventMessageScope.PublicOnly , it returns true. EventMessageScope.Private | EventMessageScope.PublicOnly ,它返回 true。

When it's not, because Private is not public, it's just half public.如果不是,因为Private不是公开的,所以它只是半公开的。

The same goes for:这同样适用于:

if(flag.Get(EventMessageScope.None))

Which returns false , except the scope is actually None ( 0x0 ), when it should always return true?哪个返回false ,除了 scope 实际上是None ( 0x0 ),什么时候它应该总是返回 true ?

Why isn't there an Enum.SetFlag like there is an Enum.HasFlag?为什么没有像 Enum.HasFlag 那样的 Enum.SetFlag?

HasFlag as a bitwise operation required more complicated logic and repeating the same flag twice HasFlag作为按位运算需要更复杂的逻辑并重复相同的标志两次

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

so MS decided to implement it.所以MS决定实施它。

SetFlag and ClearFlag are concise in C# SetFlag 和 ClearFlag 在 C# 中简洁明了

    flags |= flag;// SetFlag

    flags &= ~flag; // ClearFlag 

but unfortunately not intuitive.但不幸的是不直观。 Every time I need to set (or clear) a flag, I'm spending a few seconds (or minutes) to think: what is the name of the method?每次我需要设置(或清除)标志时,我都会花几秒钟(或几分钟)思考:方法的名称是什么? Why is it not shown in intellisense?为什么它没有在智能感知中显示? Or no, I have to use bitwise operations.或者不,我必须使用按位运算。 Note, that some developers will also ask: what is a bitwise operation?请注意,一些开发人员还会问:什么是按位运算?

Should SetFlag and ClearFlag extensions be created - YES to appear in intellisense.是否应创建 SetFlag 和 ClearFlag 扩展 - YES 出现在智能感知中。

Should SetFlag and ClearFlag extensions be used by developers - NO, because they are not efficient.开发人员是否应该使用 SetFlag 和 ClearFlag 扩展 - 不,因为它们效率不高。

We've created extensions in our library's class EnumFlagsHelper like in SomeEnumHelperMethodsThatMakeDoingWhatYouWantEasier , but named the function as SetFlag instead of Include and ClearFlag instead of Remove.我们在库的 class EnumFlagsHelper 中创建了扩展,就像在SomeEnumHelperMethodsThatMakeDoingWhatYouWantEasier中一样,但将 function 命名为 SetFlag 而不是 Include 和 ClearFlag 而不是 Remove。

In the body of SetFlag methods ( and in summary comment) I decided to add在 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")

and a similar message should be added to ClearFlag并且应该将类似的消息添加到 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  ")

I've done something that works for me and that's very simple...我做了一些对我有用的事情,这很简单......

    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;
    }

Usage:用法:

    var fa = FileAttributes.Normal;
    fa = fa.SetFlag(FileAttributes.Hidden, true);
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);
            }
        }
    }
}

Here is another quick and dirty way to SetFlag for any Enum:这是对任何 Enum 的 SetFlag 的另一种快速而肮脏的方法:

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;
    }

The & operator will give you the same answer with a & b as it will with b & a , so &运算符会给你与a & b相同的答案,因为它会给你b & a ,所以

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

is the same as writing和写一样

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

If you just want to know if the value is the same as EventMessaageScope.Public, then just use equals :如果您只想知道该值是否与 EventMessaageScope.Public相同,则只需使用equals

EventMessageScope.Private == EventMessageScope.Public EventMessageScope.Private == EventMessageScope.Public

Your method will always return false for (EventMessageScope.None).Get(EventMessaageScope.None) because None == 0 and it only returns true when the result of the AND operation is not zero.您的方法将始终为(EventMessageScope.None).Get(EventMessaageScope.None)返回false ,因为None == 0并且仅当 AND 操作的结果为零时才返回 true。 0 & 0 == 0 . 0 & 0 == 0

To answer part of your your question: the Get function works properly according to binary logic - it checks for any match.要回答您的部分问题:Get function 根据二进制逻辑正常工作 - 它会检查任何匹配项。 If you want to match the whole set of flags, consider this instead:如果要匹配整组标志,请考虑以下情况:

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

Regarding "why isn't there SetFlag"... probably because it's not really needed.关于“为什么不存在 SetFlag”......可能是因为它并不是真正需要的。 Flags are integers.标志是整数。 There is already a convention for dealing with those and it applies to flags as well.已经有处理这些的约定,它也适用于标志。 If you don't want to write it with |如果你不想用|写它and & - that's what the custom static addons are for - you can just use your own functions as you demonstrated yourself:)& - 这就是自定义 static 插件的用途 - 您可以使用自己的功能,就像您自己演示的那样:)

Enums got borked by the C language a long time ago.枚举在很久以前就被 C 语言所困扰。 Having a modicum of type safety in the C# language was important to the designers, leaving no room for an Enum.SetFlags when the underlying type can be anything between a byte and a long.在 C# 语言中具有一点类型安全性对设计人员来说很重要,当底层类型可以是字节和长整数之间的任何内容时,Enum.SetFlags 就没有空间了。 Another C induced problem btw.顺便说一句,另一个 C 引起的问题。

The proper way to deal with it is to write this kind of code inline explicitly and not try to shove it into an extension method.处理它的正确方法是显式地内联编写这种代码,而不是试图将其塞入扩展方法中。 You don't want to write a C macro in the C# language.您不想在 C# 语言中编写 C 宏。

It's 2021 and C# has a lot of nice features that mean there SHOULD be a much more elegant way of doing this.现在是 2021 年,C# 有很多不错的功能,这意味着应该有一种更优雅的方式来做到这一点。 Let's discuss the claims of previous answers...让我们讨论以前答案的主张......

CLAIM 1: Turning a flag off is inefficient because it uses two ops and invoking another method just adds more overhead.声明 1:关闭标志是低效的,因为它使用两个操作并调用另一种方法只会增加更多开销。

THIS SHOULD BE FALSE.这应该是错误的。 If you add the AggressiveInlining compiler flag, the compiler SHOULD hoist the bitwise operation to a direct inline operation.如果添加 AggressiveInlining 编译器标志,编译器应该将按位操作提升为直接内联操作。 If you are writing critical code, you may want to benchmark this to confirm since results can vary even between minor compiler versions.如果您正在编写关键代码,您可能需要对此进行基准测试以确认,因为即使在次要编译器版本之间结果也会有所不同。 But the point is, you should be able to invoke a convenience method WITHOUT paying a method lookup cost.但关键是,您应该能够调用便捷方法而无需支付方法查找成本。

CLAIM 2: Its excessively verbose because you have to set the flag and then assign the return value.权利要求 2:它过于冗长,因为您必须设置标志然后分配返回值。

THIS ALSO SHOULD BE FALSE.这也应该是错误的。 C# offers 'ref' which allows you to directly manipulate a value-type parameter by reference (in this case your enum). C# 提供'ref',它允许您通过引用(在本例中为您的枚举)直接操作值类型参数。 Combined with AggressiveInlining, the compiler should be smart enough to completely remove the ref pointers and the generated IL should look the same as if you directly inlined two bitwise operations.结合 AggressiveInlining,编译器应该足够聪明,可以完全删除 ref 指针,并且生成的 IL 看起来应该与直接内联两个按位操作一样。

CAVEATS: Of course, this is all theory.警告:当然,这都是理论。 Perhaps someone else can come along in the comments here and examine the IL from proposed code below.也许其他人可以在这里的评论中出现,并从下面提议的代码中检查 IL。 I don't have enough experience looking at IL myself (nor the time right now) to see if the hypothetical claims are true.我自己没有足够的经验(也没有现在的时间)来查看假设的说法是否正确。 But I figured this answer is still worth posting because the fact is that C# should be capable of doing what I'm explaining.但我认为这个答案仍然值得发布,因为事实是 C#应该能够做我正在解释的事情。

If someone else can confirm this, I can update the answer accordingly.如果其他人可以确认这一点,我可以相应地更新答案。

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;
}

You should be able to use the code like this:您应该能够使用这样的代码:

//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);

Even if the compiler fails to optimize this correctly, it is still extremely handy.即使编译器未能正确优化它,它仍然非常方便。 At the very least you can use it in non-critical code and expect excellent readability.至少您可以在非关键代码中使用它并期望具有出色的可读性。

Good answers so far but in case you are looking for a more performant shorthand that doesn't allocate managed memory, you can use this:到目前为止很好的答案,但如果您正在寻找一个不分配托管 memory 的性能更高的速记,您可以使用这个:

using System;
using System.Runtime.CompilerServices;
public static class EnumFlagExtensions
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static TEnum AddFlag<TEnum>(this TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
    {
        unsafe
        {
            switch (sizeof(TEnum))
            {
                case 1:
                    {
                        var r = *(byte*)(&lhs) | *(byte*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 2:
                    {
                        var r = *(ushort*)(&lhs) | *(ushort*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 4:
                    {
                        var r = *(uint*)(&lhs) | *(uint*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 8:
                    {
                        var r = *(ulong*)(&lhs) | *(ulong*)(&rhs);
                        return *(TEnum*)&r;
                    }
                default:
                    throw new Exception("Size does not match a known Enum backing type.");
            }
        }
    }
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static TEnum RemoveFlag<TEnum>(this TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
    {
        unsafe
        {
            switch (sizeof(TEnum))
            {
                case 1:
                    {
                        var r = *(byte*)(&lhs) & ~*(byte*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 2:
                    {
                        var r = *(ushort*)(&lhs) & ~*(ushort*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 4:
                    {
                        var r = *(uint*)(&lhs) & ~*(uint*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 8:
                    {
                        var r = *(ulong*)(&lhs) & ~*(ulong*)(&rhs);
                        return *(TEnum*)&r;
                    }
                default:
                    throw new Exception("Size does not match a known Enum backing type.");
            }
        }
 
    }
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void SetFlag<TEnum>(ref this TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
    {
        unsafe
        {
            fixed (TEnum* lhs1 = &lhs)
            {
                switch (sizeof(TEnum))
                {
                    case 1:
                        {
                            var r = *(byte*)(lhs1) | *(byte*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 2:
                        {
                            var r = *(ushort*)(lhs1) | *(ushort*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 4:
                        {
                            var r = *(uint*)(lhs1) | *(uint*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 8:
                        {
                            var r = *(ulong*)(lhs1) | *(ulong*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    default:
                        throw new Exception("Size does not match a known Enum backing type.");
                }
            }
        }
    }
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClearFlag<TEnum>(this ref TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
    {
        unsafe
        {
            fixed (TEnum* lhs1 = &lhs)
            {
                switch (sizeof(TEnum))
                {
                    case 1:
                        {
                            var r = *(byte*)(lhs1) & ~*(byte*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 2:
                        {
                            var r = *(ushort*)(lhs1) & ~*(ushort*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 4:
                        {
                            var r = *(uint*)(lhs1) & ~*(uint*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 8:
                        {
                            var r = *(ulong*)(lhs1) & ~*(ulong*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    default:
                        throw new Exception("Size does not match a known Enum backing type.");
                }
            }
        }
    }
}

It just requires C# 7.3 or higher and the compiler to be instructed to accept /unsafe code.它只需要 C# 7.3 或更高版本,并指示编译器接受 /unsafe 代码。

AddFlag & RemoveFlag do not modify the enum value you call this on, SetFlag and ClearFlag do modify it. AddFlag 和 RemoveFlag 不会修改您调用它的枚举值,SetFlag 和 ClearFlag 会修改它。 This is likely the generic solution to this that has the lowest performance overhead but it still won't be as fast as just directly using这可能是性能开销最低的通用解决方案,但它仍然不会像直接使用那样快

flags |= flag;
flags &= ~flag;

The reason I'm finding is that since enum is a value type, you cannot pass it in and set its type.我发现的原因是,由于 enum 是一种值类型,因此您无法将其传入并设置其类型。 To all of you that think its stupid, I say this to you: Not all developers understand bit flags and how to turn them on or off (which is much less intuitive).对于所有认为它愚蠢的人,我对你们说:并非所有开发人员都了解位标志以及如何打开或关闭它们(这不太直观)。

Not a stupid idea, just not possible.这不是一个愚蠢的想法,只是不可能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM