106

Possible Duplicate:
C# - Is there a better alternative than this to 'switch on type'?

Hello suppose i get a big if/else on class type. it's there a way to do it with a switch case ?

Example :

function test(object obj)
{
if(obj is WebControl)
{

}else if(obj is TextBox)
{

}
else if(obj is ComboBox)
{

}

etc ...

I would like to create something like

switch(obj)
{
case is TextBox:
break;
case is ComboBox:
break;

}

}

Community
  • 1
  • 1
Cédric Boivin
  • 10,854
  • 13
  • 57
  • 98
  • 1
    see also http://stackoverflow.com/questions/156467/switch-pattern-matching-idea http://stackoverflow.com/questions/7542793/how-to-use-switch-case-on-a-type http://stackoverflow.com/questions/4478464/c-sharp-switch-on-type http://stackoverflow.com/questions/298976/c-sharp-is-there-a-better-alternative-than-this-to-switch-on-type – Mikhail Poda May 01 '12 at 11:49
  • 1
    and http://stackoverflow.com/questions/94305/what-is-quicker-switch-on-string-or-elseif-on-type http://stackoverflow.com/questions/7149788/c-sharp-switch-on-object-type-at-runtime http://stackoverflow.com/questions/6304815/why-is-this-switch-on-type-case-considered-confusing http://stackoverflow.com/questions/5947343/how-to-switch-between-possible-type-of-an-object http://stackoverflow.com/questions/10115028/best-way-to-switch-behavior-based-on-type http://stackoverflow.com/questions/2551773/c-sharp-which-is-the-best-alternative-to-switch-on-type – Mikhail Poda May 01 '12 at 11:49
  • 7
    UPDATE for anyone landing on this page: The answer is yes since C# 7. You can totally write switch cases over types now. See: https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/ – LanderV May 14 '17 at 12:19
  • see example [here](https://stackoverflow.com/a/299001/7446303) – Thierry Prost May 06 '19 at 13:32

5 Answers5

168

Update C# 7

Yes: Source

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

Prior to C# 7

No.

http://blogs.msdn.com/b/peterhal/archive/2005/07/05/435760.aspx

We get a lot of requests for addditions to the C# language and today I'm going to talk about one of the more common ones - switch on type. Switch on type looks like a pretty useful and straightforward feature: Add a switch-like construct which switches on the type of the expression, rather than the value. This might look something like this:

switch typeof(e) { 
        case int:    ... break; 
        case string: ... break; 
        case double: ... break; 
        default:     ... break; 
}

This kind of statement would be extremely useful for adding virtual method like dispatch over a disjoint type hierarchy, or over a type hierarchy containing types that you don't own. Seeing an example like this, you could easily conclude that the feature would be straightforward and useful. It might even get you thinking "Why don't those #*&%$ lazy C# language designers just make my life easier and add this simple, timesaving language feature?"

Unfortunately, like many 'simple' language features, type switch is not as simple as it first appears. The troubles start when you look at a more significant, and no less important, example like this:

class C {}
interface I {}
class D : C, I {}

switch typeof(e) {
case C: … break;
case I: … break;
default: … break;
}

Link: https://blogs.msdn.microsoft.com/peterhal/2005/07/05/many-questions-switch-on-type/

Community
  • 1
  • 1
Steve
  • 31,144
  • 19
  • 99
  • 122
  • 5
    I disagree with the language designers. Lots of languages have type switches. Anyway while there is no type-switch it is easy enough to implement see http://stackoverflow.com/questions/7252186/switch-case-on-type-c/7301514#7301514 – cdiggins Sep 04 '11 at 19:08
  • 41
    Where's the rest of the answer? it now ends on "... , example like this:" – oɔɯǝɹ Oct 18 '11 at 03:54
  • 2
    I don't care what he says. It is 'simple'. Rather it would be if they designed C# better from the start. I hear there are 10+ passes for all the grammar :(. Switch is essentially obj.member. The vtable is an inaccessible member. If its treated as a value (like int) you can use it. –  Jan 05 '13 at 02:52
  • 5
    @oɔɯǝɹ I supplied the link to the source of my information if you want to read the entire article. I wasn't going to paste the entire quote when you can read it at the source. The simple answer was "No". – Steve Jan 05 '13 at 03:25
  • Added the rest of the answer :) – sventevit Apr 30 '16 at 19:39
  • 6
    UPDATE for anyone landing on this page: The answer is yes since C# 7. You can totally write switch cases over types now. See: https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/ – LanderV May 14 '17 at 12:19
  • 1
    Steve: Please update your answer. It's not longer up-to-date. – Steffen Winkler Nov 17 '17 at 16:51
  • 1
    @LanderV then I must be confused, because it looks to me that pattern matching ALWAYS takes an instance, never a type - so you still can't switch on a type AFAICT. – Ian Grainger May 03 '19 at 08:23
  • 1
    This is NOT a duplicate of that question - that is asking about an instance, this is asking about switching on a type. Which is not possible. I tried this implementation with C# 8's pattern matching, but it never matches: `return typeof(T) switch {IThingOne _ => new ThingOne(), _ => throw new NotImplementedException()};` :( – Ian Grainger May 03 '19 at 11:01
  • What needs to be understood here is that even though Microsoft isn't wrong about the implementation of a switch of complex types, it isn't necessary for that complication to remove the ability to work with primitive types. Just because I can't do everything doesn't mean I can't do anything at all. –  Nov 22 '19 at 19:02
  • switch on type are Now supported in C#9 and above,please Update your answer – Amir133 Dec 07 '20 at 20:59
  • Unfortunately, from the moment you group multiple labels that assign the cast object to a variable, they are no longer usable and give an "unassigned variable" error. – Nyerguds Sep 10 '22 at 16:56
62

The following code works more or less as one would expect a type-switch that only looks at the actual type (e.g. what is returned by GetType()).

public static void TestTypeSwitch()
{
    var ts = new TypeSwitch()
        .Case((int x) => Console.WriteLine("int"))
        .Case((bool x) => Console.WriteLine("bool"))
        .Case((string x) => Console.WriteLine("string"));

    ts.Switch(42);     
    ts.Switch(false);  
    ts.Switch("hello"); 
}

Here is the machinery required to make it work.

public class TypeSwitch
{
    Dictionary<Type, Action<object>> matches = new Dictionary<Type, Action<object>>();
    public TypeSwitch Case<T>(Action<T> action) { matches.Add(typeof(T), (x) => action((T)x)); return this; } 
    public void Switch(object x) { matches[x.GetType()](x); }
}
cdiggins
  • 17,602
  • 7
  • 105
  • 102
  • 2
    Very interesting solution... in some ways this is terrible :) ... but in some ways this is incredible (especially in the event that some other external developer could tap into this system by creating class "X" and then supplying the 'what to do with X' logic ... sorta like a mini DI/Ioc) – Timothy Khouri Sep 05 '11 at 01:28
  • 9
    When "in some ways this is terrible"? – Pedro77 Jul 18 '13 at 22:34
  • 1
    @cdiggins, is there a way I can add something like "default" or "none of them" ? – Pedro77 Jul 18 '13 at 22:51
  • @cdiggins though this is a clever camouflage, but no better than using if , else if, as it would behave when it would sequentially traverse the Case statements – Mrinal Kamboj Feb 13 '16 at 13:09
  • 1
    @Pedro77 Yes you can check the existence of the Key in the Switch method and if it doesn't exist, then use the typeof(Object) for the default case in the end of Case chain – Mrinal Kamboj Feb 13 '16 at 13:11
  • This does not work with derived types. Not good. – N73k Jun 19 '19 at 15:58
28

Yes, you can switch on the name...

switch (obj.GetType().Name)
{
    case "TextBox":...
}
Timothy Khouri
  • 31,315
  • 21
  • 88
  • 128
  • 1
    FYI, this solution won't work when given a `public class MyCustomTextBox : TextBox` – Steve Aug 31 '11 at 03:20
  • 1
    You would just do `case "TextBox: "MyCustomTextBox": ...` – Timothy Khouri Aug 31 '11 at 03:25
  • 10
    Assuming you know all possible subclasses, sure. – Steve Aug 31 '11 at 03:29
  • 3
    I imagine that the person writing this code is writing it for a reason... and knows all of the subclasses :P - Example, he's probably trying to add a CssClass name for all "textboxes" and then all "datepickers" or whatever. – Timothy Khouri Aug 31 '11 at 03:44
  • I am exaclty in the case @Steve expose, so it's not a solution for me. thanks :-( – Cédric Boivin Aug 31 '11 at 11:03
  • @Timothy "I imagine that the person writing this code is writing it for a reason... and knows all of the subclasses" - It's unlikely he'll know subclasses that haven't been written yet. – Kirk Broadhurst Sep 05 '11 at 02:00
  • 3
    I know this is old, but I like your answer, and I agree with your reasoning that the person writing this would know all of their subclasses, as long as they're using the switch statement to specify action for specific classes, and not all children classes – cost May 16 '13 at 05:06
  • 1
    this code is a workaround, as it's not checked during compilation, it can easily create a nonreachable code. – Jiří Herník Sep 17 '13 at 07:02
  • Don't use strings then expect to ever refactor! :) – Ian Grainger May 03 '19 at 08:24
15

Here's an option that stays as true I could make it to the OP's requirement to be able to switch on type. If you squint hard enough it almost looks like a real switch statement.

The calling code looks like this:

var @switch = this.Switch(new []
{
    this.Case<WebControl>(x => { /* WebControl code here */ }),
    this.Case<TextBox>(x => { /* TextBox code here */ }),
    this.Case<ComboBox>(x => { /* ComboBox code here */ }),
});

@switch(obj);

The x in each lambda above is strongly-typed. No casting required.

And to make this magic work you need these two methods:

private Action<object> Switch(params Func<object, Action>[] tests)
{
    return o =>
    {
        var @case = tests
            .Select(f => f(o))
            .FirstOrDefault(a => a != null);

        if (@case != null)
        {
            @case();
        }
    };
}

private Func<object, Action> Case<T>(Action<T> action)
{
    return o => o is T ? (Action)(() => action((T)o)) : (Action)null;
}

Almost brings tears to your eyes, right?

Nonetheless, it works. Enjoy.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • 1
    +1 definitely clever. Not sure I'd use it in production code (not that there is anything *wrong* with it per-se). – Steve Aug 31 '11 at 06:37
  • 3
    The "new [] {" and "}" are superfluous. – cdiggins Sep 04 '11 at 19:12
  • Hehe, nice one. I agree with @Steve but +1 – Doctor Jones Sep 04 '11 at 19:20
  • @cdiggins - I agree they are superflous, but I wrote the code to allow dropping the array initializer in favour of a list of parameters. The array initializer syntax is a little more readable, IMHO, once you have more than three parameters. And it looks more like the normal `switch` syntax - if you squint hard enough! – Enigmativity Sep 05 '11 at 00:28
  • 1
    @Enigmativity loved the clever solution, but as I squinted hard enough found it, same as if, else if looping, not genuinely a switch case, this would again do the chaining of calls till it finds the first case which is not null. But agreed nice camouflage. – Mrinal Kamboj Feb 13 '16 at 13:18
10

The simplest thing to do could be to use dynamics, i.e. you define the simple methods like in Yuval Peled answer:

void Test(WebControl c)
{
...
}

void Test(ComboBox c)
{
...
}

Then you cannot call directly Test(obj), because overload resolution is done at compile time. You have to assign your object to a dynamic and then call the Test method:

dynamic dynObj = obj;
Test(dynObj);
Francesco Baruchelli
  • 7,320
  • 2
  • 32
  • 40