Оператор Switch с неконстантным выражением - расширяет возможности C # / IDE - PullRequest
5 голосов
/ 27 марта 2012

Прежде чем начать критиковать и указывать на меня §8.7.2 из C # спецификация , внимательно прочитайте:)

Мы все знаем, как выглядит переключатель в C #. Итак, рассмотрим класс MainWindow с "противным" баром методом

static int barCounter = 0;
public static int Bar()
{
    return ++barCounter;
}

Где-то в этом классе у нас есть такой код

Action switchCode = () =>
{
    switch (Bar())
    {
        case 1:
            Console.WriteLine("First");
            break;
        case 2:
            Console.WriteLine("Second");
            break;
    }
};

switchCode();

switchCode();

В окне консоли мы увидим

First
Second

Используя выражения в C #, мы могли бы делать то же самое - писать много одинакового кода

var switchValue = Expression.Call(typeof(MainWindow).GetMethod("Bar"));

var WriteLine = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) });

var @switch = Expression.Switch(switchValue,
    Expression.SwitchCase(
        Expression.Call(WriteLine, Expression.Constant("First")),
        Expression.Constant(1)
        ),
    Expression.SwitchCase(
        Expression.Call(WriteLine, Expression.Constant("Second")),
        Expression.Constant(2)
        )
    );

Action switchCode = Expression.Lambda<Action>(@switch).Compile();

switchCode();

switchCode();

В DebugView мы могли видеть «код позади» этого выражения

.Switch (.Call WpfApplication1.MainWindow.Bar()) {
.Case (1):
        .Call System.Console.WriteLine("First")
.Case (2):
        .Call System.Console.WriteLine("Second")
}

Эмм, а что если мы используем Expression.Call вместо Expression.Constant?

public static bool foo1() { return false; }

public static bool foo2() { return true; }

// .....

var foo1 = Ex.Call(typeof(MainWindow).GetMethod("foo1"));
var foo2 = Ex.Call(typeof(MainWindow).GetMethod("foo2"));
var switchValue = Ex.Call(typeof(MainWindow).GetMethod("Bar"));

var WriteLine = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) });

var @switch = Ex.Switch(Ex.Constant(true),
    Ex.SwitchCase(
        Ex.Call(WriteLine, Ex.Constant("First")),
        foo1
        ),
    Ex.SwitchCase(
        Ex.Call(WriteLine, Ex.Constant("OK!")),
        Ex.Equal(switchValue, Ex.Constant(2))
        ),
    Ex.SwitchCase(
        Ex.Call(WriteLine, Ex.Constant("Second")),
        foo2
        )
    );

Action switchCode = Ex.Lambda<Action>(@switch).Compile();

switchCode();

switchCode();

Окно консоли показывает, как мы и ожидали

Second
OK!

и DebugView

.Switch (True) {
.Case (.Call WpfApplication1.MainWindow.foo1()):
        .Call System.Console.WriteLine("First")
.Case (.Call WpfApplication1.MainWindow.Bar() == 2):
        .Call System.Console.WriteLine("OK!")
.Case (.Call WpfApplication1.MainWindow.foo2()):
        .Call System.Console.WriteLine("Second")
}

Таким образом, в выражении case можно использовать неконстантное выражение:)

Хорошо, я знаю, что это маленький «грязный» код. Но тут возникает мой вопрос (наконец-то: P):
Есть ли способ расширить функциональность IDE / VisualStudio / компилятора, чтобы сделать это, но с более элегантным кодом?
Примерно так

switch (true)
{
    case foo1():
        Console.WriteLine("First");
        break;
    case Bar() == 2:
        Console.WriteLine("OK!");
        break;
    case foo2():
        Console.WriteLine("Second");
        break;
}

Я знаю, что это будет какое-то расширение, и код будет не таким (не та же производительность). Но мне интересно, возможно ли вообще «изменить» код «на лету», как анонимная функция, или возвращаемая прибыль - это преобразование во вложенный класс.

Я надеюсь, что кто-то прочитает текст выше и оставит некоторую подсказку.

Ответы [ 4 ]

2 голосов
/ 28 марта 2012

В общем, насколько мне известно, в компиляторе Microsoft C # нет точек расширения (и даже Рослин не планирует это менять).Но ничто не мешает вам написать собственный компилятор C # или, более реалистично, модифицировать Mono C # компилятор с открытым исходным кодом .

В любом случае, я думаю, что это гораздо больше проблем, чем то, что онСтоит.

Но, может быть, вы можете делать то, что вы хотите, используя что-то, что уже есть в языке, а именно, лямбда-выражения и вызовы методов, чтобы сформировать «плавный переход»:

Switch.Value(true)
    .Case(() => Foo(), () => Console.WriteLine("foo"))
    .Case(() => Bar() == 2, () => Console.WriteLine("bar == 2"));

Если выне обращайте внимания на то, что все значения условий будут оцениваться каждый раз, вы немного упростите это:

Switch.Value(true)
    .Case(Foo(), () => Console.WriteLine("foo"))
    .Case(Bar() == 2, () => Console.WriteLine("bar == 2"));
1 голос
/ 27 марта 2012

В настоящее время нет расширений, которые бы делали подобные вещи.Хотя стоит отметить, что MS SQL позволяет именно то, что вы ищете

SELECT
  Column1, Column2,
  CASE
    WHEN SomeCondition THEN Column3
    WHEN SomeOtherCondition THEN Column4
  END AS CustomColumn
FROM ...

Проблема с этим становится понимание приоритета;что происходит, когда оба условия выполняются?В SQL оператор case возвращает значение из первого оператора, которое является истинным, и игнорирует другие регистры, , но это поведение может не соответствовать желаемому .

Сила C # в том, что онневозможно закодировать переключатели таким образом, что оба случая 1 и 2 могут быть истинными одновременно, поэтому вам гарантирован только один правильный ответ.

1 голос
/ 27 марта 2012

Нет, это невозможно, и я не в курсе.Это уже своего рода чудо , что вы можете использовать string внутри switch оператора (ссылочный тип с неизменным поведением).Для таких случаев просто используйте комбинации if, if/else, if/elseif.

0 голосов
/ 27 марта 2012

Как мы все знаем, "Switches" предоставляет аналогичную функциональность для if. Большинство из нас (включая меня) считают это синтаксическим сахаром - легче прочитать несколько случаев на определенном switch, а затем прочитать несколько if / else if /.../ else. Но дело в том, что switch не является синтаксическим сахаром.

Вы должны понимать, что код, сгенерированный для switch (будь то IL или машинный код), не совпадает с кодом, сгенерированным для последовательных if. Switch имеет приятную оптимизацию, которая, как уже указывал @Ed S., позволяет ему работать в постоянное время.

...