【问题标题】:Invert enum flags反转枚举标志
【发布时间】:2014-08-09 09:35:33
【问题描述】:

假设我有以下标志:

[Flags]
public enum Foo
{
    None = 0,
    Foo1 = 1,
    Foo2 = 2,
    Foo4 = 4,
    Foo8 = 8
}

现在我有一个变量 foo:

var foo = Foo.Foo1 | Foo.Foo4;

我想要得到的是foo 的倒置标志。 这意味着这样的事情:

Foo.Foo2 | Foo.Foo8

我已经尝试过 ~ 运算符。但由于我的枚举是一个 int32 值,它会反转所有 32 位。但实际上我只需要反转我的Foo enum 使用的位。

编辑: Foo1 | Foo4 将等于以下位掩码:

00000000 00000000 00000000 00000101

如果您使用 ~ 运算符反转该位掩码,您将得到以下结果:

11111111 11111111 11111111 11111010

我想要的结果是:

00000000 00000000 00000000 00001010

如您所见。我只想反转 Foo 枚举使用的位。并非整个 32 整数值的所有位。

【问题讨论】:

  • 为了检查特定标志(即if (foo & Foo.Foo4 == Foo.Foo4) ...),您得到的结果可以正常工作。您认为这会导致什么问题?
  • 我有一个枚举指定当前按下了哪些鼠标按钮。我还需要知道哪些当前没有被按下。因此,如果我可以反转告诉我当前按下了哪些鼠标按钮的标志,那将非常有帮助。但我已经知道如何检查标志了......只需反转它。
  • 如果你反转整个值,那么只有那些没有被按下的标志会被设置;我仍然不确定仅使用~ 对您不起作用。你能发布一个代码示例来演示你不想要的行为吗?
  • 我已经编辑了我的帖子@DanPuzey
  • 我理解你的编辑,但我不明白你为什么需要这个结果。如果您尝试对结果不起作用的事情做某事,则可能有另一种方法。就目前而言,您有一堆看似多余的数据1s。

标签: c# enums bit-manipulation flags


【解决方案1】:

您要做的是将枚举的所有值组合起来,然后用当前值的补码对其进行屏蔽。

        Foo value = Foo.Foo4;
        Foo allValues = (Foo)0;
        foreach (var v in Enum.GetValues(typeof(Foo)))
            allValues |= (Foo)v;
        var compliment = allValues & ~(value);

或者,您可以将这些值与 Linq 组合并静态缓存它们以提高性能:

    public static class FooHelper
    {
        private readonly static Foo allValues = ((Foo[])Enum.GetValues(typeof(Foo))).Aggregate((Foo)0, (all, cur) => all | cur);

        public static Foo AllValues { get { return allValues ; } }
    }

然后:

        var foo = Foo.Foo1 | Foo.Foo4;
        var compliment = FooHelper.AllValues & ~(foo);

更新

如果你想要一个通用方法来组合一个枚举的所有标志值,你可以这样做:

var compliment = EnumHelper.GetAll<Foo>() & ~(value);

关于枚举的基本数据缓存在一个惰性参数化单例实例中:

/// <summary>
/// Contains generic utilities for enums, constrained for enums only.
/// </summary>
public sealed class EnumHelper : Enums<Enum>
{
    private EnumHelper()
    {
    }
}

/// <summary>
/// For use by EnumHelper, not for direct use.
/// </summary>
public abstract class Enums<TEnumBase> where TEnumBase : class, IConvertible
{
    // Generic singleton remembering basic properties about specified enums, cached for performance.
    sealed class DataSingleton<TEnum> where TEnum : struct, TEnumBase
    {
        static readonly DataSingleton<TEnum> instance = new DataSingleton<TEnum>();

        readonly bool isSigned;
        readonly TEnum allValues;
        readonly bool hasFlags;

        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static DataSingleton()
        {
        }

        DataSingleton()
        {
            isSigned = GetIsSigned();
            allValues = GetAll();
            hasFlags = GetHasFlags();
        }

        static bool GetHasFlags()
        {
            var attributes = typeof(TEnum).GetCustomAttributes(typeof(FlagsAttribute), false);
            return attributes != null && attributes.Length > 0;
        }

        static bool GetIsSigned()
        {
            var underlyingType = Enum.GetUnderlyingType(typeof(TEnum));
            bool isSigned = (underlyingType == typeof(long) || underlyingType == typeof(int) || underlyingType == typeof(short) || underlyingType == typeof(sbyte));
            bool isUnsigned = (underlyingType == typeof(ulong) || underlyingType == typeof(uint) || underlyingType == typeof(ushort) || underlyingType == typeof(byte));
            if (!isSigned && !isUnsigned)
                throw new InvalidOperationException();
            return isSigned;
        }

        static TEnum GetAll()
        {
            if (GetIsSigned())
            {
                long value = 0;
                foreach (var v in Enum.GetValues(typeof(TEnum)))
                    // Not sure I need the culture but Microsoft passes it in Enum.ToUInt64(Object value) - http://referencesource.microsoft.com/#mscorlib/system/enum.cs
                    value |= Convert.ToInt64(v, CultureInfo.InvariantCulture);
                return (TEnum)Enum.ToObject(typeof(TEnum), value);
            }
            else
            {
                ulong value = 0;
                foreach (var v in Enum.GetValues(typeof(TEnum)))
                    // Not sure I need the culture but Microsoft passes it in Enum.ToUInt64(Object value) - http://referencesource.microsoft.com/#mscorlib/system/enum.cs
                    value |= Convert.ToUInt64(v, CultureInfo.InvariantCulture);
                return (TEnum)Enum.ToObject(typeof(TEnum), value);
            }
        }

        public bool HasFlags { get { return hasFlags; } }

        public bool IsSigned { get { return isSigned; } }

        public TEnum AllValues { get { return allValues; } }

        public static DataSingleton<TEnum> Instance { get { return instance; } }
    }

    private static void ThrowOnEnumWithoutFlags<TEnum>(DataSingleton<TEnum> data) where TEnum : struct, TEnumBase
    {
        if (!data.HasFlags)
        {
            throw (new ArgumentException("The generic argument [<TEnum>] must be an enumeration with the [FlagsAttribute] applied.", "TEnum: " + typeof(TEnum).FullName));
        }
    }

    public static TEnum GetAll<TEnum>() where TEnum : struct, TEnumBase
    {
        var data = DataSingleton<TEnum>.Instance;
        ThrowOnEnumWithoutFlags<TEnum>(data);
        return data.AllValues;
    }
}

【讨论】:

  • 不错的答案,但我实际上需要更快的东西。我已经通过这样做解决了我的问题:(~(uint) PressedMouseButtons ^ 0xFFFFFFE0)。不过还是谢谢。赞成您的回答。
  • @thefiloe - 如果性能是一个问题,您可以将“allValues”缓存在一些初始化一次的只读静态中。正在更新答案...
  • @thefiloe 那么为什么不PressedMouseButtons ^ ~0xFFFFFFE0 呢?真的只有 1 次操作。
【解决方案2】:

更优雅的解决方案是使用所有字段的掩码并对其余字段执行 XOR 操作。 通常,您首先向枚举添加一个“全部”值(这样,如果添加了其他值,您就不太可能忘记更新它),所以在您的情况下:

[Flags]
public enum Foo
{
    None = 0,
    Foo1 = 1,
    Foo2 = 2,
    Foo3 = 4,
    Foo4 = 8,
    All = Foo1 | Foo2 | Foo3 | Foo4 // magic starts here
}

var foo = Foo1 | Foo4;
var fooInverted = ~Foo.All ^ ~foo; // result: Foo2|Foo3

【讨论】:

    【解决方案3】:

    要反转单个枚举标志试试这个

    private Foo foos; 
    private static void FlagInvert(Foo foo)
        {var res = foos.HasFlag(foo) ? foos &= ~foo : foos  |= foo;}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多