C# switch on type [duplicate]
Asked Answered
C

5

257

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

C# doesn't support switching on the type of an object.
What is the best pattern of simulating this:

switch (typeof(MyObj))
    case Type1:
    case Type2:
    case Type3:
Climber answered 18/12, 2010 at 14:32 Comment(10)
Or dynamic dispatch: #3902996Qualitative
Maybe see also: https://mcmap.net/q/35757/-is-there-any-benefit-to-this-switch-pattern-matching-ideaFacelift
Can you describe why you are switching on the type of an object? That would help. Also, what are the relationships amongst the types? Do they all have a common base type (other than object)? Are they all class types? Is there ever a case where an object can fall into two of your type categories? (If two of them are interfaces and the object implements both, for instance.) Are the types ever nullable value types? Enums? Delegates? Is covariance of generic delegates and interfaces ever a factor?Nick
see also https://mcmap.net/q/35757/-is-there-any-benefit-to-this-switch-pattern-matching-idea https://mcmap.net/q/35758/-switch-case-on-type-c-duplicate #7543293 #299476Aspirant
#94805 #7150288 #6305315 #5947843 #10115528 #2552273Aspirant
Curiously vb.net does allow it...Sturrock
C# 7 offers this now... Pattern Matching. Cool example reference: visualstudiomagazine.com/articles/2017/02/01/…Harrumph
Documentation of C# 7's pattern matching - learn.microsoft.com/en-us/dotnet/csharp/language-reference/…Leighleigha
@Adam, could you update the chosen answer to be gjvdkamp's answer, based on the new C# featues?Hartill
Just switch on myobj directly switch (myobj) { case MyType1: ... break; case MyType2: ... break; defauilt: throw new unsupported... }Benzofuran
E
236

See another answer; this feature now exists in C#


I usually use a dictionary of types and delegates.

var @switch = new Dictionary<Type, Action> {
    { typeof(Type1), () => ... },
    { typeof(Type2), () => ... },
    { typeof(Type3), () => ... },
};

@switch[typeof(MyType)]();

It's a little less flexible as you can't fall through cases, continue etc. But I rarely do so anyway.

Elspeth answered 18/12, 2010 at 14:32 Comment(7)
You should probably do a check if that type is in the dictionary. Could do so fairly simple like this if(@switch.ContainsKey(typeof(MyType))) @switch[typeof(MyType)](); Manatee
This should be a very efficient/performant solution. Just note that this won't work with subclasses.Auscultation
@YevgeniGrinberg I haven't tested this, but I'm certain that typeof(object) != typeof(MyType) even though MyType is a subclass of object.Auscultation
If you want a solution that works with sub-types, then at some point IsAssignableFrom will have to be used to make the comparison. This answer supports sub-types but its ussage is a little verboseAuscultation
I would rather use the Action action; if(@switch.TryGetValue(typeof(Type1), out action) action(); instead of searching twice.. (contains & indexers)Ronn
If you want to support sub-classes (or any other more complex requirement) simply write a custom IEqualityComparer<Type> and implement it's Equals method as desired - although I must confess writing a good GetHash could be difficult - of the top of my head I can't really think of any obvious hash implementation that supports equality of sub-classes... Word of warning: If you don't compare sub-classing both ways (up and down the type hierarchy) you could get weird results that depend on the order in which types are added to the dictionary! (And technically it would be invalid equality.)Allx
There are new features of C# that allow this now. See this answer: https://mcmap.net/q/35760/-c-switch-on-type-duplicateHystero
R
274

Update:

This got fixed in C# 7.0 with pattern matching

switch (MyObj) {
    case Type1 t1: 
    case Type2 t2:
    case Type3 t3:
}

Old answer:

It is a hole in C#'s game, no silver bullet yet.

You should google on the 'visitor pattern' but it might be a little heavy for you but still something you should know about.

Here's another take on the matter using Linq: http://community.bartdesmet.net/blogs/bart/archive/2008/03/30/a-functional-c-type-switch.aspx

Otherwise something along these lines could help

// nasty..
switch(MyObj.GetType().ToString()){
  case "Type1": etc
}

// clumsy...
if myObj  is Type1 then
if myObj is Type2 then

etc.

Residency answered 18/12, 2010 at 14:39 Comment(14)
The problem with the functional c-type switch is that it is not getting the precompiled speed that the actual switch-case syntax gets. This can lull the programmer into overusing this switch- look-alike class thinking that it gets the same advantages. This looks like a glamorous wrapper for a more expensive if-then-with-lambdas.Skyline
In the case of testing for types, I would actually prefer testing with the if-then as I don't have to mess with strings. One simple if-then against @Mark's solution: if (typeTests.Keys.Contains(TypeToTest)) and you have something probably comparable to performance with switch-case (because of the hashed keys) and not near as error prone, IMO.Skyline
I liked this method as a way to help generate moq models for testingSeleucia
Sorry, but the ToString seems like a bad plan - as renaming types using Visual Studio will break this. Why not typeOf(Type1).Cassock
I'm calling it clumsy.. Right now the way to go is pattern matching, I'll update this answerResidency
If you're worried about catching type renames as strings, you should be using nameof(Type1) instead of string literals. Actually, you should be doing that anyways any time a string is really representing the name of a type or property.Ronnyronsard
This does not answer the question. The question was about switching on Type, not about switching on the type of an object.Antoninaantonino
The code example literally switches on the type of MyObj.Residency
Could you update this to show how to switch on an actual Type instance? To have something like switch (typeInstance) { case int: { ... } }Kruller
You can do that with normal overloading, have a method with different overloads for each type you want to handle.Residency
How does this work with public SomeGenericMethod<T>() {switch (typeof(T) ...? It just gives the compile error An expression of type 'Type' cannot be handled by a pattern of type ...Norwood
So this was supposed to have been added in 7.0, correct? Because VS is throwing a compiler error telling me Feature 'type pattern' is unavailable in 8.0 and that I'd need to use 9.0+ instead.Salmonoid
The Strategy pattern could also work in this context.Melodic
@Norwood see stackoverflow.com/a/299001 and stackoverflow.com/questions/44905Predicate
E
236

See another answer; this feature now exists in C#


I usually use a dictionary of types and delegates.

var @switch = new Dictionary<Type, Action> {
    { typeof(Type1), () => ... },
    { typeof(Type2), () => ... },
    { typeof(Type3), () => ... },
};

@switch[typeof(MyType)]();

It's a little less flexible as you can't fall through cases, continue etc. But I rarely do so anyway.

Elspeth answered 18/12, 2010 at 14:32 Comment(7)
You should probably do a check if that type is in the dictionary. Could do so fairly simple like this if(@switch.ContainsKey(typeof(MyType))) @switch[typeof(MyType)](); Manatee
This should be a very efficient/performant solution. Just note that this won't work with subclasses.Auscultation
@YevgeniGrinberg I haven't tested this, but I'm certain that typeof(object) != typeof(MyType) even though MyType is a subclass of object.Auscultation
If you want a solution that works with sub-types, then at some point IsAssignableFrom will have to be used to make the comparison. This answer supports sub-types but its ussage is a little verboseAuscultation
I would rather use the Action action; if(@switch.TryGetValue(typeof(Type1), out action) action(); instead of searching twice.. (contains & indexers)Ronn
If you want to support sub-classes (or any other more complex requirement) simply write a custom IEqualityComparer<Type> and implement it's Equals method as desired - although I must confess writing a good GetHash could be difficult - of the top of my head I can't really think of any obvious hash implementation that supports equality of sub-classes... Word of warning: If you don't compare sub-classing both ways (up and down the type hierarchy) you could get weird results that depend on the order in which types are added to the dictionary! (And technically it would be invalid equality.)Allx
There are new features of C# that allow this now. See this answer: https://mcmap.net/q/35760/-c-switch-on-type-duplicateHystero
A
29

There is a simple answer to this question which uses a dictionary of types to look up a lambda function. Here is how it might be used:

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

There is also a generalized solution to this problem in terms of pattern matching (both types and run-time checked conditions):

var getRentPrice = new PatternMatcher<int>()
    .Case<MotorCycle>(bike => 100 + bike.Cylinders * 10) 
    .Case<Bicycle>(30) 
    .Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
    .Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
    .Default(0);

var vehicles = new object[] {
    new Car { EngineType = EngineType.Diesel, Doors = 2 },
    new Car { EngineType = EngineType.Diesel, Doors = 4 },
    new Car { EngineType = EngineType.Gasoline, Doors = 3 },
    new Car { EngineType = EngineType.Gasoline, Doors = 5 },
    new Bicycle(),
    new MotorCycle { Cylinders = 2 },
    new MotorCycle { Cylinders = 3 },
};

foreach (var v in vehicles)
{
    Console.WriteLine("Vehicle of type {0} costs {1} to rent", v.GetType(), getRentPrice.Match(v));
}
Aerophyte answered 4/9, 2011 at 20:29 Comment(0)
S
3

I have used this form of switch-case on rare occasion. Even then I have found another way to do what I wanted. If you find that this is the only way to accomplish what you need, I would recommend @Mark H's solution.

If this is intended to be a sort of factory creation decision process, there are better ways to do it. Otherwise, I really can't see why you want to use the switch on a type.

Here is a little example expanding on Mark's solution. I think it is a great way to work with types:

Dictionary<Type, Action> typeTests;

public ClassCtor()
{
    typeTests = new Dictionary<Type, Action> ();

    typeTests[typeof(int)] = () => DoIntegerStuff();
    typeTests[typeof(string)] = () => DoStringStuff();
    typeTests[typeof(bool)] = () => DoBooleanStuff();
}

private void DoBooleanStuff()
{
   //do stuff
}

private void DoStringStuff()
{
    //do stuff
}

private void DoIntegerStuff()
{
    //do stuff
}

public Action CheckTypeAction(Type TypeToTest)
{
    if (typeTests.Keys.Contains(TypeToTest))
        return typeTests[TypeToTest];

    return null; // or some other Action delegate
}
Skyline answered 18/12, 2010 at 15:3 Comment(0)
G
2

I did it one time with a workaround, hope it helps.

string fullName = typeof(MyObj).FullName;

switch (fullName)
{
    case "fullName1":
    case "fullName2":
    case "fullName3":
}
Gigantism answered 18/12, 2010 at 14:42 Comment(9)
This approach is fragile if you rename/move any of the classes.Despumate
A little refactoring and you are screwed.Tenantry
Yeah, but I can not add: case typeof(Bitmap).FullName:Obadias
This is less fragile in C# 6.0 using nameof: case nameof(<classname>)Magnusson
@Magnusson - Can you please give an example of how to write this? As far as I am aware, this would only give you the name of the variable, not the class name?Omdurman
You would use nameof(<classname>) instead of nameof(<variablename>). For instance, in the example above, nameof(MyObj). However I was mistaken as this is switching on the full name, whereas nameof will only give you the class name.Magnusson
Why not use case case nameof(MyClass): break; so that it is more refactorableGym
@Dzivo The switch construct in C# only accepts literals as its cases.Plumbago
@Plumbago #30098205 i just tried it using c# 9.0 and .net 6.0 maybe some older versions dont support it ?Gym

© 2022 - 2024 — McMap. All rights reserved.