Путаница с разбором Enum - PullRequest
       16

Путаница с разбором Enum

7 голосов
/ 04 февраля 2011

Я конвертирую числовое значение строкового типа в соответствующий Enum.Пока я тестировал свой код, я обнаружил интересное поведение, которое меня смущает.

С помощью приведенного ниже примера кода кто-нибудь может пролить свет на то, почему не генерируется исключение, если / когда переменная "s" имеет значение, которое не соответствует ни одному из значений Enum?Кроме того, как можно установить для переменной sEnum значение, которое не существует в определении перечисления Stooge?

class Program
{
    enum Stooge
    {
        Unspecified,
        Moe,
        Larry,
        Curly,
        Shemp
    }

    static void Main(string[] args)
    {
        while (true)
        {
            Console.WriteLine("Enter a number...");

            string s = Console.ReadLine();
            Stooge sEnum = (Stooge)(int.Parse(s)); //Why doesn't this line throw if s != 0, 1, 2, 3, or 4?

            Console.WriteLine("\r\nYou entered: {0}\r\nEnum String Value: {1}\r\nEnum Int Value: {2}\r\n", s, sEnum.ToString(), (int)sEnum);
        }
    }
}

Ответы [ 5 ]

9 голосов
/ 04 февраля 2011

Это было решение со стороны людей, создавших .NET.Перечисление поддерживается другим типом значения (int, short, byte и т. Д.), Поэтому оно может иметь любое значение, допустимое для этих типов значений.

Лично я не являюсьПоклонник того, как это работает, поэтому я сделал ряд служебных методов:

/// <summary>
/// Utility methods for enum values. This static type will fail to initialize 
/// (throwing a <see cref="TypeInitializationException"/>) if
/// you try to provide a value that is not an enum.
/// </summary>
/// <typeparam name="T">An enum type. </typeparam>
public static class EnumUtil<T>
    where T : struct, IConvertible // Try to get as much of a static check as we can.
{
    // The .NET framework doesn't provide a compile-checked
    // way to ensure that a type is an enum, so we have to check when the type
    // is statically invoked.
    static EnumUtil()
    {
        // Throw Exception on static initialization if the given type isn't an enum.
        Require.That(typeof (T).IsEnum, () => typeof(T).FullName + " is not an enum type.");
    }

    /// <summary>
    /// In the .NET Framework, objects can be cast to enum values which are not
    /// defined for their type. This method provides a simple fail-fast check
    /// that the enum value is defined, and creates a cast at the same time.
    /// Cast the given value as the given enum type.
    /// Throw an exception if the value is not defined for the given enum type.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumValue"></param>
    /// <exception cref="InvalidCastException">
    /// If the given value is not a defined value of the enum type.
    /// </exception>
    /// <returns></returns>
    public static T DefinedCast(object enumValue)

    {
        if (!System.Enum.IsDefined(typeof(T), enumValue))
            throw new InvalidCastException(enumValue + " is not a defined value for enum type " +
                                           typeof (T).FullName);
        return (T) enumValue;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="enumValue"></param>
    /// <returns></returns>
    public static T Parse(string enumValue)
    {
        var parsedValue = (T)System.Enum.Parse(typeof (T), enumValue);
        //Require that the parsed value is defined
        Require.That(parsedValue.IsDefined(), 
            () => new ArgumentException(string.Format("{0} is not a defined value for enum type {1}", 
                enumValue, typeof(T).FullName)));
        return parsedValue;
    }

    public static bool IsDefined(T enumValue)
    {
        return System.Enum.IsDefined(typeof (T), enumValue);
    }

}

public static class EnumExtensions
{
    public static bool IsDefined<T>(this T enumValue)
        where T : struct, IConvertible
    {
        return EnumUtil<T>.IsDefined(enumValue);
    }
}

Таким образом, я могу сказать:

if(!sEnum.IsDefined()) throw new Exception(...);

... или:

EnumUtil<Stooge>.Parse(s); // throws an exception if s is not a defined value.

Обновление

Как отметил в комментариях Брэндон Крамер, C # 7.3 представил некоторые новые универсальные типы, которые позволяют заменять where T : struct, IConvertible выше на where T : Enum, чтобы улучшить время компиляции.проверка перечисления передаваемого типа. Таким образом, вы можете избавиться от оператора guard в статическом конструкторе EnumUtil.

2 голосов
/ 04 февраля 2011

Перечисление - это технически просто int (или то, что вы определили для базового типа перечисления). Вы можете проверить соответствующее значение в перечислении, хотя с помощью вызова Enum.IsDefined. Более подробная информация здесь: Приведите int к перечислению в C #

1 голос
/ 04 февраля 2011

Enum - действительно тонкая оболочка над int. В основном это int + статическая коллекция возможных значений (вид констант). Все проверки выполняются во время компиляции, проверки типов и т. Д. Но когда вы действительно приводите значения int к enum времени выполнения, это не волнует. Так что подтвердите свой вклад!

0 голосов
/ 02 февраля 2019

Я изменил реализацию с https://stackoverflow.com/a/4892571/275388 для устранения двух проблем

  1. Подпись DefinedCast(object enumValue) указывает, что код можно использовать с типами string и int (итакже напрасно помечает более поздние).
  2. Enum.IsDefined / Enum.Parse оба выделяют массив через Enum.GetValues(typeof(TEnum)), что фактически привело к замедлению игл для моего варианта использования - этого можно избежать за счет кэширования карты.

Следовательно, я оказался с

public static class EnumExtensions
{
    public static TEnum DefinedCast<TEnum>(string value)
        where TEnum : struct, IComparable, IFormattable, IConvertible
    {
        if (!MapByString<TEnum>.Instance.TryGetValue(value, out TEnum @enum))
        {
            throw new InvalidCastException(FormattableString.Invariant($"'{value}' is not a defined value"));
        }

        return @enum;
    }

    public static TEnum DefinedCast<TEnum>(int value)
        where TEnum : struct, IComparable, IFormattable, IConvertible
    {
        if (!MapByInteger<TEnum>.Instance.TryGetValue(value, out TEnum @enum))
        {
            throw new InvalidCastException(FormattableString.Invariant($"'{value}' is not a defined value"));
        }

        return @enum;
    }

    private static class MapByInteger<TEnum>
        where TEnum : struct, IComparable, IFormattable, IConvertible
    {
        public static readonly Dictionary<int, TEnum> Instance = ((TEnum[])Enum.GetValues(typeof(TEnum))).ToDictionary(e => (int)Convert.ChangeType(e, typeof(int), CultureInfo.InvariantCulture));
    }

    private static class MapByString<TEnum>
        where TEnum : struct, IComparable, IFormattable, IConvertible
    {
        public static readonly Dictionary<string, TEnum> Instance = ((TEnum[])Enum.GetValues(typeof(TEnum))).ToDictionary(e => e.ToString(CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase);
    }
}
0 голосов
/ 04 февраля 2011

Используйте int.Parse (), если вы не хотите создавать исключение в случае, если переданное значение не может быть проанализировано.Используйте int.TryParse (), если вы не хотите анализировать значение, которое может быть недопустимым без исключения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...