Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Switch statement with non-constant-expression - Extends C#/IDE ability

Before you start criticizing and pointing me §8.7.2 of C# specification, read carefully :)

We all know how switch looks like in C#. Ok so consider the class MainWindow with "nasty" Bar method

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

Somewhere in this class we have code like this

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

switchCode();

switchCode();

In the console window we will see

First
Second

Using Expressions in C# we could do the same – writing much same code

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();

In DebugView we could see "code behind" this expression

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

Ehmm, what if we use Expression.Call instead 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();

Console window shows, as we expected

Second
OK!

And 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")
}

So it is possible to use non-constant expression in case-statement :)

Ok, I known this is little ‘messy’ code. But here comes my question (finally :P):
Is there any way to extends functionality of IDE/VisualStudio/compiler to do this, but with more elegant code?
Something like this

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

I know this will be some extension and code will be not the same (not the same performance). But I wonder if this is even possible to "change" code on-the-fly – like anonymous function or yield return is transform to nested class.

I hope someone goes through the above text and leave some clue.

like image 590
Carnifex Avatar asked Mar 26 '12 21:03

Carnifex


2 Answers

In general, there are no extension points in the Microsoft C# compiler as far as I know (and even Roslyn isn't planning to change that). But nothing is stopping you from writing your own C# compiler, or, more realistically, modifying the open source Mono C# compiler.

In any case, I think it's much more trouble than what it's worth.

But maybe you can do what you want using something that's already in the language, namely, lambdas and method calls, to form a “fluent switch”:

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

If you don't mind that all the condition values will be evaluated every time, you simplify that a bit:

Switch.Value(true)
    .Case(Foo(), () => Console.WriteLine("foo"))
    .Case(Bar() == 2, () => Console.WriteLine("bar == 2"));
like image 191
svick Avatar answered Oct 15 '22 00:10

svick


No, it's not possible, nor that I'm aware of. It's already kind of miracle that you can use a string inside switch statement (reference type with immutable behavior). For these kind of cases just use if, if/else, if/elseif combinations.

like image 28
Tigran Avatar answered Oct 14 '22 23:10

Tigran