Some days ago I wrote an overview of typeswitch problem.
Now it’s time to give some solutions.


Let’s imagine a document inheritance hierarchy:

XsltDocument : XmlDocument : Document
TextDocument : Document

Let’s assume I want to get strings “Xml”, “Xslt”, “Not Xml and not Xslt” based on the document runtime type.
This is primitive indeed, but it does demonstrate a concept.

I call the most useful soultion fluent switch:

string result = Switch.Type(document).
            (XsltDocument d) => "Xslt"
            (XmlDocument d) => "Xml"
            d => "Not Xml and not Xslt"

It does contain a lot of visual clutter, but it scales quite well in comparison to if/return approach.
The code behind is simple — each Case checks type and returns itself.
Case is a generic method whose parameters are inferred from the lambda.

For this task, case bodies do not depend on actual object contents.
So they can be expressed cleaner:

string result = Switch.Type(document).To<string>().
        Otherwise("Not Xml and not Xslt").

This time I have to specify the result type explicitly.

The fluent switch syntax is quite powerful — you can even add cases dynamically.
This is a nice difference from conventional language constructs.


I have tried a number of alternatives, but no one of them did better.

  1. Many overloads switch
    string result = Switch.Type(
            (XsltDocument d) => "Xslt",
            (XmlDocument d) => "Xml",
            d => "Not Xml and not Xslt"

    This syntax is quite concise and understandable.
    But it requires an additional overload for each additional case.
    So it is quite impractical.

  2. Object initializer switch
    string result = new TypeSwitch<Document, string>(document) {
        (XsltDocument d) => "Xslt",
        (XmlDocument d) => "Xml",
        d => "Not Xml and not Xslt"

    Also more concise than my original solution, but much more cryptic.
    Constructor and generic parameters also add a degree of confusion.

    The most interesting thing about this syntax was that it actually worked.
    It seems object initializers have some nice fluent power.


After running some benchmarks, I found that fluent switch is about 200 times slower than hardcoded ifs.
It may be perfectly acceptable, of course.
However, I have found a way to precompile the switch using expression trees.

From the usage perspective, precompiled switch is just a Func<T, TResult> (it does not support Actions right now).
So you can cache it in

private static readonly Func<Document, string> CompiledFluentLambdaSwitch = Switch.Type<Document>().To<string>().
        (XsltDocument d) => "Xslt"
        (XmlDocument d) => "Xml"
        d => "Not Xml and not Xslt"

which is extremely similar to the first code sample.
The differences are that you do not specify what you are switching on (it would be a function parameter).
But you do explicitly specify from/to types.

The compilation process was fun to write, since it was the first time I dug into expressions trees.
Statements are not supported in trees, so I had to use embedded ConditionalExpressions for cases.
The resulting tree is something like

d => (d is XsltDocument)
             ? ((cast => "Xslt")(d as XsltDocument)) 
             : ((d is XmlDocument) 
                   ? ((...

I have not found a way to cache cast and null-check it, so I cast/typecheck it two times.


The best thing about compilation is performance:

Benchmark: 1000000 iterations, two switch calls per iteration.

Benchmark overhead:            40.1ms   30.0ms   30.0ms   30.0ms |    32.5ms
Direct cast:                   80.1ms   60.1ms   80.1ms   60.1ms |    70.1ms
Fluent switch
    on lambdas:              1512.2ms 1502.2ms 1602.3ms 1482.1ms |  1524.7ms
    on lambdas (compiled):     90.1ms  110.2ms   80.1ms   80.1ms |    90.1ms
    on constants:            1281.8ms 1271.8ms 1311.9ms 1271.8ms |  1284.3ms
    on constants (compiled):   80.1ms   90.1ms   90.1ms   70.1ms |    82.6ms
Many overloads switch:        440.6ms  390.6ms  430.6ms  420.6ms |   420.6ms
Object initializer switch:    751.1ms  681.0ms  741.1ms  751.1ms |   731.1ms

As you can see, precompiled switch is nearly as performant as hardcoded one (direct cast).
I am quite impressed by simplicity/power ratio of the expression trees.


I uploaded AshMind.Constructs to Google Code.
I see it as a learning/research project, but you can put it to any practical use.