Четыре странности switch
В C# оператор C# немножко странен. Сегодня мы кратко коснёмся четырёх вещей, которых вы, вероятно, не знаете об операторе switch.
Случай 1:
Вы, вероятно, знаете, что запрещено «проваливаться» из одной секции switch в другую:
switch(attitude)
{
case Attitude.HighAndMighty:
Console.WriteLine("High");
// мы хотим провалиться, но это ошибка
case Attitude.JustMighty:
Console.WriteLine("Mighty");
break;
}
Но, быть может, вы не знали, что можно принудительно передать управление дальше при помощи goto:
switch(attitude)
{
case Attitude.HighAndMighty:
Console.WriteLine("High");
goto case Attitude.JustMighty;
case Attitude.JustMighty:
Console.WriteLine("Mighty");
break;
}
Довольно мило! Семантически, секции case – всего лишь метки, на которые switch выполняет условный переход; мы позволяем вам делать прямой переход, если вы хотите.
Случай 2:
Частая и сбивающая с толку ошибка, которую пишущие на C# программисты C (вроде меня!) делают постоянно:
switch(attitude)
{
case Attitude.HighAndMighty:
Console.WriteLine("High and Mighty");
break;
case Attitude.JustMighty:
Console.WriteLine("Just Mighty");
break;
default:
// Ничего не делать
}
Это ошибка, потому что вы пытаетесь «провалиться» из секции default. Собственно, тут некуда «проваливаться», но компилятор придирчив насчёт этого. Он требует, чтобы каждая секция switch, включая последнюю, имела недостижимую точку выхода. Цель этого правила, и правила «не проваливайся» в целом, – в том, что мы хотим дать вам возможность произвольно переупорядочивать ваши секции в switch без случайного внесения ломающих изменений. Исправить этот код можно, сделав «ничего не делать» явным при помощи инструкции break.
Это особенно сбивает с толку потому, что некоторые люди интерпретируют сообщение об ошибке, как гласящее, что проблема – в попадании внутрь секции default, в то время как на самом деле проблема в выпадении из секции default.
Случай 3:
Как я ранее обсуждал, пространство деклараций – это область кода, в которой две объявленные вещи не могут называться одинаково. Цикл foreach неявно определяет своё отдельное пространство деклараций, поэтому вот так – можно:
foreach(var blah in blahs) { ... }
foreach(var blah in otherblahs) { ... }
Блоки switch тоже определяют свои отдельные пространства деклараций, а вот секции switch – нет:
switch(x)
{
case OneWay:
int y = 123;
FindYou(ref y);
break;
case TheOther:
double y = 456.7; // нельзя!
GetchaGetcha(ref y);
break;
}
Вы можете решить эту проблему несколькими способами; возможно, простейший – обернуть тело секции в фигурные скобки:
switch(x)
{
case OneWay:
{
int y = 123;
FindYou(ref y);
break;
}
case TheOther:
{
double y = 456.7; // можно!
GetchaGetcha(ref y);
break;
}
}
что говорит компилятору «нет, правда, я хочу, чтобы они были разными пространствами деклараций».
Если у вас есть переменная, которую вы хотите объявить однажды и использовать в куче разных мест, то это легально, хоть и несколько странно:
switch(x)
{
case OneDay:
string s;
SeeYa(out s);
break;
case NextWeek:
s = "hello"; // можно, мы используем декларацию выше.
Meetcha(ref s);
break;
}
}
Случай 4:
Забавное следствие правил «анализа достижимости» в спецификации – в том, что этот фрагмент программы некорректен:
int M(bool b)
{
switch(b)
{
case true: return 1;
case false: return 0;
}
}
Конечно, в реальности вы скорее запишете это как намного более понятное «return b ? 1 : 0;», но не должна ли такая программа быть корректной? Она некорректна потому, что анализатор достижимости рассуждает вот так: поскольку нет секции default, то switch может выбрать вариант, не соответствующий ни одному из case. Так что точка выхода из switch достижима, и, значит, у нас есть метод, возвращающий int, с достижимым путём исполнения кода, который выходит из метода и ничего не возвращает.
Угу, анализатор достижимости не слишком сообразителен. Он не понимает, что есть только два возможных пути исполнения и мы покрыли все из них инструкциями return. И, конечно, если вы делаете switch по байту и у вас есть секции case для каждой из 256 возможностей, мы, опять же, не замечаем, что switch исчерпывающе полон.
Этот недостаток дизайна языка – глупый, но, честно говоря, у нас есть более важные приоритеты, чем исправление этого глупого случая. Если вы встретитесь с этой неудачной ситуацией, просто пометьте одну из секций «default:» и всё будет хорошо.
-- Эрик в отпуске; эта статья была записана заранее. --
Comments
- Anonymous
June 06, 2010
В C# оператор C# немножко странен. наверное имелось в ввиду В C# оператор switch немножко странен.