Как определить представленный тип значения перечисления?

Рассмотрим следующие два перечисления:

enum MyEnum1 {
    Value1 = 1,
    Value2 = 2,
    Value3 = 3
}

enum MyEnum2 {
     Value1 = 'a',
     Value2 = 'b',
     Value3 = 'c'
}

Я могу получить физическое значение, представленное этими значениями перечисления, посредством явного каста, ((int)MyEnum1.Value2) == 2 или ((char)MyEnum2.Value2) == 'b', но что, если я хочу получить представление char или представление int, не зная сначала тип,?

Можно ли получить базовое значение перечисления без каста или возможно ли, по меньшей мере, программно определить правильный тип базового значения?

Ответ 1

Базовое значение обоих этих перечислений int. Вы просто используете тот факт, что во втором случае происходит неявное преобразование от char до int.

Например, если вы посмотрите на второе перечисление в Reflector, вы увидите что-то вроде этого:

internal enum MyEnum2
{
    Value1 = 0x61,
    Value2 = 0x62,
    Value3 = 0x63
}

EDIT: Если вы хотите использовать другой базовый тип, вы должны указать его, например.

public enum Foo : long
{
}

Однако только теги byte, sbyte, short, ushort, int, uint, long и ulong являются допустимыми типами перечислений.

Ответ 2

Все перечисления должны использовать в своем объявлении один из следующих типов: byte, sbyte, short, ushort, int, uint, long или ulong. Вот как вы указываете тип:

enum MyEnum3 : long {
  Value1 = 5L,
  Value2 = 9L,
  Value3 = long.MaxValue
}

Если вы не укажете тип, по умолчанию будет int.

К сожалению, вы не можете указать char как базовый тип. Вы можете создать это "расширение" в качестве настраиваемого атрибута:

[AttributeUsage(AttributeTargets.Enum)]
public class CharAttribute : Attribute { }

[Char] enum MyEnum2 {
  Value1 = 'a',
  Value2 = 'b',
  Value3 = 'c'
}

И тогда у вас есть класс:

public static class EnumEx {
  public static Type GetUnderlyingType(Type enumType) {
    if (!enumType.IsEnum) throw new ArgumentException();
    if (enumType.GetCustomAttributes(typeof(CharAttribute), false).Length > 0) {
      return typeof(char);
    }
    return Enum.GetUnderlyingType(enumType);
  }
  public static object ConvertToUnderlyingType(object enumValue) {
    return Convert.ChangeType(enumValue,
      GetUnderlyingType(enumValue.GetType()));
  }
}

(Кстати, метод Enum.GetUnderlyingType кажется тем, который вы искали, но он никогда не возвращает char, потому что вы можете 't имеет char перечисления на языке.)

Это позволит вам перейти к расширенному понятию char enums:

var value3 = EnumEx.ConvertToUnderlyingType(MyEnum2.Value3);
Console.WriteLine(value3);

Откроется c на консоли.

Обратите внимание на базовый тип и значения ваших перечислений char, они должны идеально вписываться в char, чтобы избежать ошибок преобразования (переполнения). Безопасные типы - 16-разрядные (например, char) или менее: byte, sbyte, short или ushort. Другие типы в порядке, если значения в перечислении могут быть преобразованы в 16-разрядные символы без потери точности (как в примере выше).

Использование базового типа по умолчанию (int) и char литералов в качестве значений enum (которые неявно конвертируются в int) достаточно хороши.

UPDATE:

Вы можете объявить перечисление char в F #:

namespace Drawing

type Color =
   | Red = 'r'
   | Green = 'g'
   | Blue = 'b'

В С# вы можете использовать его следующим образом:

Console.WriteLine(Enum.GetUnderlyingType(typeof(Color)));

И он напечатает System.Char.

Но... С# будет жаловаться, если вы попытаетесь использовать его значения. Это:

Console.WriteLine(Color.Red.ToString());

Дает ошибку компилятора:

ошибка CS0570: "Drawing.Color.Red" не поддерживается языком

В VB.NET нет ошибки компиляции, но есть ошибка времени выполнения из Enum.GetName. Похоже, что среда выполнения не готова к использованию перечислений char. Это выдержка из этого метода (из Reflector):

if (((!type.IsEnum && (type != intType)) && ((type != typeof(short)) && (type != typeof(ushort)))) && (((type != typeof(byte)) && (type != typeof(sbyte))) && (((type != typeof(uint)) && (type != typeof(long))) && (type != typeof(ulong)))))
  {
    throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnumBaseTypeOrEnum"), "value");
  }

Он проверяет не только, что тип является перечислением, но также и одним из вышеупомянутых базовых типов (для которых char не является опцией). Таким образом, вы действительно не должны создавать char перечисления в F #. Вы могли бы использовать описанный мной подход "расширения".

Ответ 3

И перечисление - это просто тип int за кулисами. Вы можете изменить его на длинный, байтовый и некоторые другие, но они должны быть числами. Char работает только потому, что его можно просто изменить на int. Так что, извините, я не думаю, что это возможно.

Ответ 4

Попробуйте это, работает для меня:

public static bool IsCharEnum(this Type T)
{
    var enumType = T.IsNullableEnum() ? Nullable.GetUnderlyingType(T) : T;
    if (!enumType.IsEnum) return false;
    try
    {
        return ((int[])Enum.GetValues(enumType)).All(i => char.IsLetter((char)i));
    }
    catch
    {
        return false;
    }
}

public static bool IsNullableEnum(this Type T)
{
    var u = Nullable.GetUnderlyingType(T);
    return (u != null) && u.IsEnum;
}

Добавьте этот код в класс расширений, затем вы можете использовать его как:

if (propValue.GetType().IsCharEnum())
{
    propValue = ((char)Convert.ToInt32(propValue)).ToString();
}

Когда propValue имеет тип MyEnum2, значение будет изменено на char и будет 'a', 'b' или 'c'