Switching on types in C#

Many programming languages have it, and many people would like to see this in C#: the ability to do switch on types. While the following example is far fetched and shows bad programming practices, if it would ever be implemented, I’d expect it to look like this:

class A { string Name { get; } }
class B : A { string LongName { get; } }
class C : A { string FullName { get; } }
class X { public string ToString(IFormatProvider provider); }
class Y { public string GetIdentifier(); }

public string GetName(object value)
{
    switch typeof(value)
    {
        case C: return value.FullName;
        case B: return value.LongName;
        case A: return value.Name;
        case X: return value.ToString(CultureInfo.CurrentCulture);
        case Y: return value.GetIdentifier();
        default: return value.ToString();
    }
}

Notice how the case C and case B have to be specified before case A, as the code would match the first. This is against the definition of switch in C#, which states that the order of the cases does not matter. A generalization of this concept and its problems are discussed in this rather old MSDN blog post by Peter Hallam.


However, it is likely that you’d want to use just such a type switching construct in your code. Instead of writing a whole string of if-statements, you can do this by using this static class I wrote:

public static class TypeSwitch
{
    public static Switch<TSource> On<TSource>(TSource value)
    {
        return new Switch<TSource>(value);
    }

    public class Switch<TSource>
    {
        private TSource value;
        private bool handled = false;

        internal Switch(TSource value)
        {
            this.value = value;
        }

        public Switch<TSource> Case<TTarget>(Action<TTarget> action)
            where TTarget : TSource
        {
            if (!handled)
            {
                var sourceType = value.GetType();
                var targetType = typeof(TTarget);
                if (targetType.IsAssignableFrom(sourceType))
                {
                    action((TTarget)value);
                    handled = true;
                }
            }

            return this;
        }

        public void Default(Action<TSource> action)
        {
            if (!handled)
                action(value);
        }
    }
}

Download the full, commented source

The example at the start of the post will be written like this:

public string GetName(object value)
{
    string name = null;
    TypeSwitch.On(operand)
        .Case((C x) => name = x.FullName)
        .Case((B x) => name = x.LongName)
        .Case((A x) => name = x.Name)
        .Case((X x) => name = x.ToString(CultureInfo.CurrentCulture))
        .Case((Y x) => name = x.GetIdentifier())
        .Default((x) => name = x.ToString());
    return name;
}

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>