14

我需要检测是否在枚举值中设置了标志,该类型用标志属性标记。

通常它是这样制作的:

(value & flag) == flag

但是由于我需要通过泛型来执行此操作(有时在运行时我事件只有一个“枚举”引用。我找不到使用 & 运算符的简单方法。目前我是这样的:

    public static bool IsSet<T>(this T value, T flags) where T : Enum
    { 
        Type numberType = Enum.GetUnderlyingType(typeof(T));

        if (numberType.Equals(typeof(int)))
        {
            return BoxUnbox<int>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(sbyte)))
        {
            return BoxUnbox<sbyte>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(byte)))
        {
            return BoxUnbox<byte>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(short)))
        {
            return BoxUnbox<short>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(ushort)))
        {
            return BoxUnbox<ushort>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(uint)))
        {
            return BoxUnbox<uint>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(long)))
        {
            return BoxUnbox<long>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(ulong)))
        {
            return BoxUnbox<ulong>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(char)))
        {
            return BoxUnbox<char>(value, flags, (a, b) => (a & b) == b);
        }
        else
        {
            throw new ArgumentException("Unknown enum underlying type " + numberType.Name + ".");
        }
    }


    private static bool BoxUnbox<T>(object value, object flags, Func<T, T, bool> op)
    {
        return op((T)value, (T)flags);
    }

但我不喜欢永无止境的 if - else 块,那么有没有办法转换这些值,我可以使用 & 运算符或任何其他解决方案来检查这个?

4

7 回答 7

15

对我来说,它看起来过于复杂。这个怎么样(记住枚举总是映射到整数值类型):

public static bool IsSet<T>(T value, T flags) where T : struct
{
    // You can add enum type checking to be perfectly sure that T is enum, this have some cost however
    // if (!typeof(T).IsEnum)
    //     throw new ArgumentException();
    long longFlags = Convert.ToInt64(flags);
    return (Convert.ToInt64(value) & longFlags) == longFlags;
}
于 2009-07-06T12:17:33.320 回答
10

I wrote a set of extension methods for enums, in case you need it :

public static class EnumExtensions
{
    private static void CheckEnumWithFlags<T>()
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
        if (!Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
            throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
    }

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct
    {
        CheckEnumWithFlags<T>();
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flag);
        return (lValue & lFlag) != 0;
    }

    public static IEnumerable<T> GetFlags<T>(this T value) where T : struct
    {
        CheckEnumWithFlags<T>();
        foreach (T flag in Enum.GetValues(typeof(T)).Cast<T>())
        {
            if (value.IsFlagSet(flag))
                yield return flag;
        }
    }

    public static T SetFlags<T>(this T value, T flags, bool on) where T : struct
    {
        CheckEnumWithFlags<T>();
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flags);
        if (on)
        {
            lValue |= lFlag;
        }
        else
        {
            lValue &= (~lFlag);
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static T SetFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, true);
    }

    public static T ClearFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, false);
    }

    public static T CombineFlags<T>(this IEnumerable<T> flags) where T : struct
    {
        CheckEnumWithFlags<T>();
        long lValue = 0;
        foreach (T flag in flags)
        {
            long lFlag = Convert.ToInt64(flag);
            lValue |= lFlag;
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }
}

The main drawback is that you can't specify where T : Enum : it is explicitly forbidden ("Constraint cannot be special class 'System.Enum'"), so the extension methods will appear in intellisense for all structs... I added the CheckEnumWithFlags method to check that the type is actually an enum, and has the Flags attribute.


UPDATE : Jon Skeet recently started an interesting library called UnconstrainedMelody which does exactly the same sort of things, and works around the generic type constraint limitation mentioned above

于 2009-07-06T12:36:21.093 回答
7

这应该为enum具有任何基础类型的类型完成工作:

public static bool IsSet<T>(this T value, T flags) where T : struct
{
    return (Convert.ToInt64(value) & Convert.ToInt64(flags)) ==
        Convert.ToInt64(flags);
}

Convert.ToInt64使用 64 位整数是可能的“最宽”整数类型,所有枚举值都可以转换为(偶数ulong)。请注意,这char不是有效的基础类型。似乎它在 C# 中无效,但通常在 CIL/CLR 中有效。

此外,您不能对枚举(即where T : struct)强制执行泛型类型约束;您可以做的最好的事情是使用where T : struct强制T成为值类型,然后可选地执行动态检查以确保它T是枚举类型。

为了完整起见,这是我非常简短的测试工具:

static class Program
{
    static void Main(string[] args)
    {
        Debug.Assert(Foo.abc.IsSet(Foo.abc));
        Debug.Assert(Bar.def.IsSet(Bar.def));
        Debug.Assert(Baz.ghi.IsSet(Baz.ghi));
    }

    enum Foo : int
    {
        abc = 1,
        def = 10,
        ghi = 100
    }

    enum Bar : sbyte
    {
        abc = 1,
        def = 10,
        ghi = 100
    }

    enum Baz : ulong
    {
        abc = 1,
        def = 10,
        ghi = 100
    }
}
于 2009-07-06T12:16:05.753 回答
6

就个人而言,我认为这看起来不错,因为您已将其包装成一个单一用途的函数。如果您将该代码分散在整个程序中,我认为您会遇到一些问题,但是您创建的内容提高了使用它的任何地方的清晰度,并且函数本身足够清楚它的作用。

当然只是我的意见。

不过,您可以使用 is 关键字,这可能会有所帮助

public static bool IsSet<T>(this T value, T flags) where T : Enum
{ 
    if (value is int)
    {
        return ((int)(object)a & (int)(object)b) == (int)(object)b);
    }
    //etc...
于 2009-07-06T12:13:12.243 回答
2

只需使用Enum.HasFlag()方法!

于 2010-08-12T10:16:15.180 回答
0

或... public static bool IsSet(this Enum value, Enum compare) { int baseValue = value.ToInt32(); int compareValue = compare.ToInt32(); if (baseValue == 0) 返回假;return ((baseValue & compareValue) == compareValue); }

于 2009-07-28T08:21:09.903 回答
0

我用它来比较标志

public static bool IsSet<T>(this T input, T match)
{
    return (Convert.ToUInt32(input) & Convert.ToUInt32(match)) != 0;
}

在这里您可以进行不同的转换。从int到short到long。

于 2009-07-06T12:16:38.017 回答