Сравнение флагов перечисления в C #

Мне нужно определить, установлен ли флаг в пределах значения enum, и этот тип помечен атрибутом Flag.

Обычно это делается так:

(value & flag) == flag

Но так как мне нужно сделать это по универсальному (иногда во время выполнения у меня событие есть только ссылка "Enum". Я не могу найти простой способ использовать оператор &. На данный момент я делаю это так:

    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, так есть ли способ приведения этих значений, чтобы я мог использовать оператор & или любое другое решение, чтобы проверить это?

Ответы на вопрос(7)

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

Просто используйтеEnum.HasFlag() Метод!

Я использовал это, чтобы сравнить флаги

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

Здесь вы можете сделать различные преобразования. От int к короткому к длинному.

Лично я думаю, что это выглядит хорошо, потому что вы обернули его в одну целевую функцию. Если бы этот код был разбросан по всей программе, я думаю, у вас были бы некоторые проблемы, но то, что вы создали, улучшает ясность везде, где он используется, и сама функция достаточно ясна, что она делает.

Просто мое мнение, конечно.

Вы можете использовать ключевое слово 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...

Для меня это выглядит слишком сложным. Как насчет этого (учитывая, что enum всегда отображается на тип целочисленного значения):

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

если вам это нужно:

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

Основным недостатком является то, что вы не можете указатьwhere T : Enum : это явно запрещено («Ограничение не может быть специальным классом» System.Enum »), поэтому методы расширения будут появляться в intellisense для всех структур ... Я добавилCheckEnumWithFlags метод, чтобы проверить, что тип на самом деле является перечислением, и имеетFlags приписывать.

ОБНОВЛЕНИЕ: Джон Скит недавно запустил интересную библиотеку под названиемUnconstrainedMelody который делает то же самое, и работает вокруг ограничения ограничения общего типа, упомянутого выше

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). Note that char is not a valid underlying type. Кажется, что этоnot действует в C #, но этоis в целом действует в 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
    }
}

ВАШ ОТВЕТ НА ВОПРОС