Способ разбора строки .NET enum или значения int, связанного с 'Flags'

Существует отличный способ выяснить элемент перечисления, используя следующий подход:

// memberType is enum type
if (Enum.IsDefined(memberType, valueString))
{
    return Enum.Parse(memberType, valueString);
}
else
{
    try 
    {
        var underlyingValue = Convert.ChangeType(valueString, Enum.GetUnderlyingType(memberType));
        if (Enum.IsDefined(memberType, underlyingValue))
        {
            return underlyingValue;
        }
    }
    catch...
}

Это работает как шарм. За исключением значений, построенных из перечислений, отмеченных FlagsAttribute. Например, для этого перечисления и значения:

[Flags]
enum MyEnum {
    One = 0x1,
    Two = One << 1,
    Four = One << 2,
    Eight = One << 3
}

var e = MyEnum.One | MyEnum.Eight;

описанный выше подход не работает. Похоже, единственный способ заставить его работать - попытаться получить все значения перечисления и побитовые И с их входным значением. Это несколько утомительно. Так вы знаете лучший способ?

Ответ:

Последний способ выглядит следующим образом:

var parsed = Enum.Parse(memberType, valueString);
decimal d;
if (!decimal.TryParse(parsed.ToString(), out d))
{
    return parsed;
}
throw new ArgumentOutOfRangeException(memberInfo.Name, valueString, "Bad configuration parameter value.");

Ответы

Ответ 1

Я думаю, что лучше задать вопрос, как определить плохие значения.

Похоже, что есть хорошая работа, найденная в С# 4.0 в двух словах. Из здесь. После того как вы проанализируете целочисленное значение для перечисления, вы можете использовать его и посмотреть, действительно ли значение. Это будет работать для комбинированных флагов.

static bool IsFlagDefined(Enum e)
{
    decimal d;
    return !decimal.TryParse(e.ToString(), out d);
}

Ответ 2

Это ожидаемое поведение, так как вы можете получить значения, которые не соответствуют флагам. Например, допустим значение a = 1, b = 2, c = 4, d = 8 и т.д. (Только стандартная двоичная прогрессия). Возможно иметь значение 5 для значения (a и c) или 7 (a, b и c).

Ответ 3

Почему бы вам просто не использовать Enum.Parse()

var test = MyEnum.One | MyEnum.Eight;
var str = test.ToString();
var back = (MyEnum) enum.Parse(typeof(MyEnum), str); // this returns MyEnum.One | MyEnum.Eight

Сначала попробуйте преобразовать вашу строку в "тип несвязания", если она была успешно выполнена - отбросьте ее в MyEnum и то, что вам нужно. Если конвертировать не удалось - попробуйте использовать Enum.Parse. Если он также терпит неудачу - это очень плохой вход (исключение throw)


Update: Не требуется тестирование для преобразования int enum.Parse(typeof(MyEnum), "9") возвращает MyEnum.One | MyEnum.Eight (проверено в Framework 2.0 до 4.0)


Обновление 2: Таким образом, вопрос действительно был "Как определить плохие ценности?". Грязное решение:

var res = Enum.Parse(targetType, str);
bool isBad;
try
{
    Convert(res.ToString(), underType);
    isBad = true;
}
catch
{
    // convert failed
    isBad = false;
}

if (isBad)
    throw new Exeption();
return res;

Но это очень грязно, а исключение исключения - это дорогостоящая операция, поэтому здесь есть штраф за производительность... Но он работает)

Ответ 4

Используйте Enum.Parse. Затем проверьте возвращаемое значение, чтобы не были установлены биты, которые не являются допустимыми флагами. Для этого создайте битовую маску, содержащую все допустимые значения, и OR вместе со значением. Если результат отличается от маски, были установлены некоторые биты, которые не являются допустимыми флагами. Это не слишком утомительно (но вам нужно будет проверить, действительно ли Enum является перечислением Flags, прежде чем применять эту логику - если это не перечисление Flags, используйте IsDefined).

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

object value = Enum.Parse(memberType, valueString);
int numericValue = (int)value;
int definedMask = Enum.GetValues(memberType).Cast<int>().Aggregate(0, (v,a) => v | a);
if ((definedMask | numericValue) != definedMask)
    throw new InvalidOperationException(String.Format("{0} is not a valid {1} value.", valueString, memberType.Name));
return value;

Ответ 5

Это хороший вопрос, ваше предложение

попытайтесь получить все значения перечисления и побитовое И их с входным значением

после 10 минут исследований выглядит действительно разумным.. ^^

Я нашел хорошую ссылку на аналогичную проблему, парень там потратил довольно много времени, чтобы написать эту статью, может быть, вам тоже будет интересно.

Разрыв С# Флаги перечислены в отдельные значения для сравнения