Switch case on type c# [duplicate]
Asked Answered
R

5

108

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;

}

}

Repentant answered 31/8, 2011 at 3:15 Comment(4)
see also https://mcmap.net/q/35757/-is-there-any-benefit-to-this-switch-pattern-matching-idea #7543293 https://mcmap.net/q/35760/-c-switch-on-type-duplicate #299476Jibber
and #94805 #7150288 #6305315 #5947843 #10115528 #2552273Jibber
UPDATE for anyone landing on this page: The answer is yes since C# 7. You can totally write switch cases over types now. See: blogs.msdn.microsoft.com/dotnet/2016/08/24/…Incinerator
see example hereOsterman
H
174

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/

Hotien answered 31/8, 2011 at 3:18 Comment(12)
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 https://mcmap.net/q/35758/-switch-case-on-type-c-duplicate/…Metternich
Where's the rest of the answer? it now ends on "... , example like this:"Scapolite
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.Naoma
@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".Hotien
Added the rest of the answer :)Alexaalexander
UPDATE for anyone landing on this page: The answer is yes since C# 7. You can totally write switch cases over types now. See: blogs.msdn.microsoft.com/dotnet/2016/08/24/…Incinerator
Steve: Please update your answer. It's not longer up-to-date.Allegro
@Incinerator 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.Liddell
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()}; :(Liddell
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.Boice
switch on type are Now supported in C#9 and above,please Update your answerLissalissak
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.Loy
M
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); }
}
Metternich answered 4/9, 2011 at 19:7 Comment(6)
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)Raine
When "in some ways this is terrible"?Capsaicin
@cdiggins, is there a way I can add something like "default" or "none of them" ?Capsaicin
@Metternich 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 statementsBuffum
@Capsaicin 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 chainBuffum
This does not work with derived types. Not good.Magness
R
28

Yes, you can switch on the name...

switch (obj.GetType().Name)
{
    case "TextBox":...
}
Raine answered 31/8, 2011 at 3:18 Comment(9)
FYI, this solution won't work when given a public class MyCustomTextBox : TextBoxHotien
You would just do case "TextBox: "MyCustomTextBox": ...Raine
Assuming you know all possible subclasses, sure.Hotien
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.Raine
I am exaclty in the case @Hotien expose, so it's not a solution for me. thanks :-(Abominable
@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.Dais
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 classesRoentgen
this code is a workaround, as it's not checked during compilation, it can easily create a nonreachable code.Sig
Don't use strings then expect to ever refactor! :)Liddell
P
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.

Plash answered 31/8, 2011 at 5:40 Comment(5)
+1 definitely clever. Not sure I'd use it in production code (not that there is anything wrong with it per-se).Hotien
The "new [] {" and "}" are superfluous.Metternich
Hehe, nice one. I agree with @Hotien but +1Triiodomethane
@Metternich - 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!Plash
@Plash 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.Buffum
D
11

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);
Dewan answered 31/8, 2011 at 4:55 Comment(1)
Cool solution! Got my vote :-)Unhair

© 2022 - 2024 — McMap. All rights reserved.