Вопрос по generics, c# – Сравнение флагов перечисления в C #

13

Error: User Rate Limit Exceeded

Error: User Rate Limit Exceeded

(value & flag) == flag

Error: User Rate Limit Exceeded

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

Error: User Rate Limit Exceeded

Еще одна причина, по которой C # требует общего ограничения перечисления:stackoverflow.com/questions/7244 Keith
Почти похожий вопрос можно найти здесь:stackoverflow.com/questions/746905/… Kirtan

Ваш Ответ

7   ответов
0

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

2

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

Да, теперь с .net4.0;) но использовать его нужно осторожно, потому что он очень медленный. Enyra
Проблема заключается в том, что вы сравниваете набор флагов с другим набором флагов ... если вы просто используете операнды, вы можете узнать, совпадают ли все флаги, или установлен определенный флаг (HasFlag делает это тоже) .. ... но это становится сложным, когда вам нужно только, чтобы одно из значений флага присутствовало в другом наборе значений флага ... Thomas GetFlags & lt; T & gt; метод помогает нам сделать это.
0

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

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

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

Не правильное утверждение.
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...
Да, я виноват. Я забыл, что именно поэтому я использовал класс Convert вместо прямого приведения :)
Я держу это, как будто я это реализовал первым. Enyra
FWIW, вы не можете приводить универсальный тип непосредственно к типу значения, таким образом, приведение дополнительного объекта.
Смотрите ответ Ронни здесь:stackoverflow.com/questions/7244
Слишком много ненужных отливок.
15

Для меня это выглядит слишком сложным. Как насчет этого (учитывая, что 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;
}
@tvanfosson: Короче, на мой взгляд, опустить объявление переменной, не менее читабельное. Это однако педантичность и семантика ...
@ Noldorin, конечно, это довольно просто, даже тривиально, поэтому здесь так много ответов :)
Да, так что это в основном то же самое, что и сейчас. Не уверен, что я бы потрудилсяConvert.ToInt64(flags) в любом случае, так как компилятор C # все равно его оптимизирует.
Я попробовал это с двумя перечислениями с множественными значениями, и это не сработало.
10

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

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 который делает то же самое, и работает вокруг ограничения ограничения общего типа, упомянутого выше

[+1] Я искал способ перечислить установленные флаги; мой код использует ту же технику, что и ваши GetFlags & lt; T & gt; только метод не является расширением. Прискорбно, что C # не может указать, где T: Enum. Без метода расширения вы можете просто использовать встроенную функцию HasFlag ().
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). 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
    }
}
Неправильное утверждение, потому что флаги могут иметь несколько установленных битов.
@tvanfosson: Вы правы. Я был глуп и редактировал код прямо в сети, а не в Visual Studio. ;)
Я удалил свой исходный комментарий, но все еще не уверен, что это абсолютно правильно, поскольку правая часть теста все еще является Enum. Я считаю, что это тоже нужно преобразовать.
Char не является допустимым базовым типом ... в C #. В его вопросе не указано, кто создал перечисление и на каком языке.
@arbiter: Это совершенно правильно, если вы проверяете только один флаг. Действительно, проверка на наличие & quot; == флагов & quot; работает в более общем случаеcombinations флагов.

Похожие вопросы