Обычно можно было бы ожидать и надеяться, что два приведения необходимы, чтобы сначала распаковать тип значения, а затем выполнить некоторое преобразование типа значения в другой тип значения. Вот пример, где это выполняется:
// create boxed int
IFormattable box = 42; // box.GetType() == typeof(int)
// unbox and narrow
short x1 = (short)box; // fails runtime :-)
short x2 = (short)(int)box; // OK
// unbox and make unsigned
uint y1 = (uint)box; // fails runtime :-)
uint y2 = (uint)(int)box; // OK
// unbox and widen
long z1 = (long)box; // fails runtime :-)
long z2 = (long)(int)box; // OK (cast to long could be made implicit)
Как видно из моих смайликов, я рад, что эти преобразования не пройдут, если я использую только одно приведение. В конце концов, попытка распаковать тип значения в другой тип значения за одну операцию, вероятно, является ошибкой кодирования.
(В интерфейсе IFormattable
нет ничего особенного; вы также можете использовать класс object
, если хотите.)
Однако сегодня я понял, что с перечислениями все по-другому (когда (и только когда) перечисления имеют один и тот же базовый тип). Вот пример:
// create boxed DayOfWeek
IFormattable box = DayOfWeek.Monday; // box.GetType() == typeof(DayOfWeek)
// unbox and convert to other
// enum type in one cast
DateTimeKind dtk = (DateTimeKind)box; // succeeds runtime :-(
Console.WriteLine(box); // writes Monday
Console.WriteLine(dtk); // writes Utc
Я считаю такое поведение неудачным. На самом деле должно быть обязательно говорить (DateTimeKind)(DayOfWeek)box
. Читая спецификацию C#, я не вижу оправдания этой разнице между числовыми преобразованиями и преобразованиями перечисления. Такое ощущение, что безопасность типа теряется в этой ситуации.
Считаете ли вы, что это "неопределенное поведение", которое можно было бы улучшить (без изменения спецификаций) в будущей версии .NET? Это было бы критическим изменением.
Кроме того, если поставщик любого из типов перечисления (в моем примере DayOfWeek
или DateTimeKind
) решит изменить базовый тип одного из типов перечисления с int
на что-то другое (может быть long
, short
,...), то вдруг приведенный выше однократный код перестанет работать, что кажется глупым.
Конечно, перечисления DayOfWeek
и DateTimeKind
не являются особыми. Это могут быть любые перечисления, в том числе пользовательские.
Несколько связано: Почему распаковка перечислений дает странные результаты? (распаковывает int
непосредственно в перечисление)
ДОПОЛНЕНИЕ:
Хорошо, так много ответов и комментариев были сосредоточены на том, как перечисления обрабатываются «под капотом». Хотя это само по себе интересно, я хочу больше сосредоточиться на том, охвачено ли наблюдаемое поведение спецификацией C#.
Предположим, я написал тип:
struct YellowInteger
{
public readonly int Value;
public YellowInteger(int value)
{
Value = value;
}
// Clearly a yellow integer is completely different
// from an integer without any particular color,
// so it is important that this conversion is
// explicit
public static explicit operator int(YellowInteger yi)
{
return yi.Value;
}
}
а потом сказал:
object box = new YellowInteger(1);
int x = (int)box;
тогда говорит ли спецификация С# что-нибудь о том, удастся ли это во время выполнения? Мне все равно, .NET может рассматривать YellowInteger
как просто Int32
с метаданными другого типа (или как там это называется), но может ли кто-нибудь гарантировать, что .NET не "перепутает" YellowInteger
и Int32
при распаковке? Итак, где в спецификации C# я могу увидеть, удастся ли (int)box
(вызов моего явного метода оператора)?
E1
к любому другому типу перечисленияE2
. Это не говорит о том, что приведение становится неявным, если тип перечисленияE1
и тип перечисленияE2
(различны, но) имеют один и тот же базовый тип. И, конечно же, поскольку всякая упаковка/распаковка исключена, вы не можете назначитьDayOfWeek
наDateTimeKind
без явного приведения. - person Jeppe Stig Nielsen   schedule 13.07.2012