Is there a better alternative than this to 'switch on type'?
Asked Answered
W

32

412

Seeing as C# can't switch on a Type (which I gather wasn't added as a special case because is relationships mean that more than one distinct case might apply), is there a better way to simulate switching on type other than this?

void Foo(object o)
{
    if (o is A)
    {
        ((A)o).Hop();
    }
    else if (o is B)
    {
        ((B)o).Skip();
    }
    else
    {
        throw new ArgumentException("Unexpected type: " + o.GetType());
    }
}
Wamble answered 18/11, 2008 at 15:4 Comment(2)
K
292

Switching on types is definitely lacking in C# (UPDATE: in C#7 / VS 2017 switching on types is supported - see Zachary Yates's answer). In order to do this without a large if/else if/else statement, you'll need to work with a different structure. I wrote a blog post awhile back detailing how to build a TypeSwitch structure.

https://learn.microsoft.com/archive/blogs/jaredpar/switching-on-types

Short version: TypeSwitch is designed to prevent redundant casting and give a syntax that is similar to a normal switch/case statement. For example, here is TypeSwitch in action on a standard Windows form event

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));

The code for TypeSwitch is actually pretty small and can easily be put into your project.

static class TypeSwitch {
    public class CaseInfo {
        public bool IsDefault { get; set; }
        public Type Target { get; set; }
        public Action<object> Action { get; set; }
    }

    public static void Do(object source, params CaseInfo[] cases) {
        var type = source.GetType();
        foreach (var entry in cases) {
            if (entry.IsDefault || entry.Target.IsAssignableFrom(type)) {
                entry.Action(source);
                break;
            }
        }
    }

    public static CaseInfo Case<T>(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            Target = typeof(T)
        };
    }

    public static CaseInfo Case<T>(Action<T> action) {
        return new CaseInfo() {
            Action = (x) => action((T)x),
            Target = typeof(T)
        };
    }

    public static CaseInfo Default(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            IsDefault = true
        };
    }
}
Kehr answered 18/11, 2008 at 15:44 Comment(4)
"type == entry.Target" can also be changed to "entry.Target.IsAssignableFrom(type)" to take compatible types (e.g., subclasses) into account.Tellurion
Altered the code to use "entry.Target.IsAssignableFrom(type)" so that subclasses are supported.Unbiased
One thing maybe worth noting is that (from what I understand) it is required to specify the 'default' action last to ensure all other cases are checked. I believe this is not a requirement in a standard switch - not that I have ever seen anybody try to plant a 'default' anywhere other than the bottom anyway. A couple of fail safe options for this could be to order the array to ensure default is last (bit wasteful) or pop the default in a variable to be processed after the foreach (which would only ever happen if a match wasn't found)Bikini
I ended up using an approach like this all the time, so I have wrapped it up in a in project github.com/asgerhallas/ShinySwitch - and on nuget too: nuget.org/packages/ShinySwitch.Rivard
F
443

With C# 7, which shipped with Visual Studio 2017 (Release 15.*), you are able to use Types in case statements (pattern matching):

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

With C# 6, you can use a switch statement with the nameof() operator (thanks @Joey Adams):

switch(o.GetType().Name) {
    case nameof(AType):
        break;
    case nameof(BType):
        break;
}

With C# 5 and earlier, you could use a switch statement, but you'll have to use a magic string containing the type name... which is not particularly refactor friendly (thanks @nukefusion)

switch(o.GetType().Name) {
  case "AType":
    break;
}
Fusion answered 18/11, 2008 at 15:8 Comment(3)
I don't like this answer because nameof(NamespaceA.ClassC) == nameof(NamespaceB.ClassC) is true.Intercession
(c# 7) you can also use underscore if you don't need access to the object: case UnauthorizedException _:Cartel
@ischas, that is a very important point. Thank you! However using nameof is better than ToString because of the compiler protection.Lancewood
K
292

Switching on types is definitely lacking in C# (UPDATE: in C#7 / VS 2017 switching on types is supported - see Zachary Yates's answer). In order to do this without a large if/else if/else statement, you'll need to work with a different structure. I wrote a blog post awhile back detailing how to build a TypeSwitch structure.

https://learn.microsoft.com/archive/blogs/jaredpar/switching-on-types

Short version: TypeSwitch is designed to prevent redundant casting and give a syntax that is similar to a normal switch/case statement. For example, here is TypeSwitch in action on a standard Windows form event

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));

The code for TypeSwitch is actually pretty small and can easily be put into your project.

static class TypeSwitch {
    public class CaseInfo {
        public bool IsDefault { get; set; }
        public Type Target { get; set; }
        public Action<object> Action { get; set; }
    }

    public static void Do(object source, params CaseInfo[] cases) {
        var type = source.GetType();
        foreach (var entry in cases) {
            if (entry.IsDefault || entry.Target.IsAssignableFrom(type)) {
                entry.Action(source);
                break;
            }
        }
    }

    public static CaseInfo Case<T>(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            Target = typeof(T)
        };
    }

    public static CaseInfo Case<T>(Action<T> action) {
        return new CaseInfo() {
            Action = (x) => action((T)x),
            Target = typeof(T)
        };
    }

    public static CaseInfo Default(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            IsDefault = true
        };
    }
}
Kehr answered 18/11, 2008 at 15:44 Comment(4)
"type == entry.Target" can also be changed to "entry.Target.IsAssignableFrom(type)" to take compatible types (e.g., subclasses) into account.Tellurion
Altered the code to use "entry.Target.IsAssignableFrom(type)" so that subclasses are supported.Unbiased
One thing maybe worth noting is that (from what I understand) it is required to specify the 'default' action last to ensure all other cases are checked. I believe this is not a requirement in a standard switch - not that I have ever seen anybody try to plant a 'default' anywhere other than the bottom anyway. A couple of fail safe options for this could be to order the array to ensure default is last (bit wasteful) or pop the default in a variable to be processed after the foreach (which would only ever happen if a match wasn't found)Bikini
I ended up using an approach like this all the time, so I have wrapped it up in a in project github.com/asgerhallas/ShinySwitch - and on nuget too: nuget.org/packages/ShinySwitch.Rivard
Z
105

One option is to have a dictionary from Type to Action (or some other delegate). Look up the action based on the type, and then execute it. I've used this for factories before now.

Zita answered 18/11, 2008 at 15:7 Comment(5)
Minor note: good for 1:1 matches, but might be a pain with inheritance and/or interfaces - especially as order isn't guaranteed to be preserved with a dictionary. But still, it is the way I do it in a fair few places ;-p So +1Anthropolatry
@Marc: How would inheritance or interfaces break in this paradigm? Assuming the key is a type, and the action is a method, then inheritance or interfaces should actually force the Right Thing(TM), as far as I can tell. I certainly understand the issue with multiple actions and lack of ordering.Afrikander
This technique breaks down for inheritance and interfaces because you need a one-to-one correspondence between the object you're checking and the delegate you're calling. Which of an object's multiple interfaces should you try to find in the dictionary?Boisvert
If you're building a dictionary specifically for this purpose you could overload the indexer to return the key type's value, or if missing then its superclass, if that's missing then that superclass, etc., until there's nothing left.Dupe
So if I have an inheritance chain like: Object > Foo > Bar > Blee - and I have an action defined for 'Foo' then a lookup against either 'Blee' or 'Bar' would find the action designated for 'Foo'.Dupe
T
49

With JaredPar's answer in the back of my head, I wrote a variant of his TypeSwitch class that uses type inference for a nicer syntax:

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)
{
    string name = null;
    TypeSwitch.On(value)
        .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;
}

Note that the order of the Case() methods is important.


Get the full and commented code for my TypeSwitch class. This is a working abbreviated version:

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

    public sealed class Switch<TSource>
    {
        private readonly 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 (!this.handled && this.value is TTarget)
            {
                action((TTarget) this.value);
                this.handled = true;
            }
            return this;
        }

        public void Default(Action<TSource> action)
        {
            if (!this.handled)
                action(this.value);
        }
    }
}
Tsana answered 5/4, 2012 at 8:46 Comment(5)
@Virtlink Thanks for this static helper, I made a small fork of your gist for compatibility with Silverlight (Windows Phone) and this.value readonly.Chaplin
You can also add an extension method for the initial case: public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource . This lets you say value.Case((C x) ...Kopp
Also, I think you can simply say value is TTarget instead of all that IsAssignableFrom stuff.Kopp
@JoeyAdams: I incorporated your last suggestion, along with some small improvements. However, I'm keeping the syntax the same.Tsana
@Virtlink: Cool. I agree with leaving out the Case extension method, so anyone who runs across code using TypeSwitch sees a clear indication that a utility class is being used. I suppose you could make a TypeSwitch extension method with the same signature as On (so you can say value.TypeSwitch().Case((C x) ...), but it's not a big deal.Kopp
D
25

You can use pattern matching in C# 7 or above:

switch (foo.GetType())
{
    case var type when type == typeof(Player):
        break;
    case var type when type == typeof(Address):
        break;
    case var type when type == typeof(Department):
        break;
    case var type when type == typeof(ContactType):
        break;
    default:
        break;
}
Drayage answered 25/1, 2019 at 21:40 Comment(2)
Thank you for this one! Can be used to detect subclasses as well: if (this.TemplatedParent.GetType().IsSubclassOf(typeof(RadGridView))) can be changed to: switch (this.TemplatedParent.GetType()) case var subRadGridView when subRadGridView.IsSubclassOf(typeof(RadGridView)):Oujda
You are doing it wrong. See the answer by Serge Intern and read about Liskov substitution principleVaporish
N
19

C# 8 pattern matching enhancements made it possible to do it like this. This syntax is more concise.

public Animal Animal { get; set; }

var animalName = Animal switch
{
    Cat cat => "Tom",
    Mouse mouse => "Jerry",
    _ => "unknown"
};
Notch answered 14/1, 2020 at 8:50 Comment(2)
you do not need the identifier if it's unused. but otherwise, this should be the top answer now.Roughandready
Anyway to do similar thing on Generic parameter T?Moonraker
S
14

Create a superclass (S) and make A and B inherit from it. Then declare an abstract method on S that every subclass needs to implement.

Doing this the "foo" method can also change its signature to Foo(S o), making it type safe, and you don't need to throw that ugly exception.

Sidney answered 18/11, 2008 at 15:7 Comment(2)
True bruno, but the question doesn't suggest that. You could include that in your answer though Pablo.Lune
From the question I think A and B are generic enough that they can be A = String ; B = List<int> for instance ...Offbeat
K
11

Yes, thanks to C# 7 that can be achieved. Here's how it's done (using expression pattern):

switch (o)
{
    case A a:
        a.Hop();
        break;
    case B b:
        b.Skip();
        break;
    case C _: 
        return new ArgumentException("Type C will be supported in the next version");
    default:
        return new ArgumentException("Unexpected type: " + o.GetType());
}
Krisha answered 5/4, 2017 at 13:53 Comment(0)
K
8

If you were using C# 4, you could make use of the new dynamic functionality to achieve an interesting alternative. I'm not saying this is better, in fact it seems very likely that it would be slower, but it does have a certain elegance to it.

class Thing
{

  void Foo(A a)
  {
     a.Hop();
  }

  void Foo(B b)
  {
     b.Skip();
  }

}

And the usage:

object aOrB = Get_AOrB();
Thing t = GetThing();
((dynamic)t).Foo(aorB);

The reason this works is that a C# 4 dynamic method invocation has its overloads resolved at runtime rather than compile time. I wrote a little more about this idea quite recently. Again, I would just like to reiterate that this probably performs worse than all the other suggestions, I am offering it simply as a curiosity.

Kalgoorlie answered 19/11, 2008 at 15:22 Comment(1)
I had the same idea today. It's roughly 3 times slower than switching on the type name. Of course slower is relative ( for 60,000,000 calls, only 4 seconds. ), and the code is so much more readable it's well worth it.Seligmann
I
7

You should really be overloading your method, not trying to do the disambiguation yourself. Most of the answers so far don't take future subclasses into account, which may lead to really terrible maintenance issues later on.

Inobservance answered 18/11, 2008 at 15:16 Comment(4)
Overload resolution is determined statically so that just won't work at all.Brigittebriley
@Neutrino: there is nothing in the question that dictates that the type isn't known at compile time. And if it is, an overload makes way more sense than any other option, given the OP's original code example.Miserere
I think the fact that he's trying to use an 'if' or 'switch' statement to determine the type is a pretty clear indication that the type is not known at compile time.Brigittebriley
@Neutrino, I remember you that, as Sergey Berezovskiy pointed out, there is the dynamic keyword in C#, which represents a type that has to be dynamically solved (at runtime, rather than compile-time).Pimentel
T
7

For built-in types, you can use the TypeCode enumeration. Please note that GetType() is kind of slow, but probably not relevant in most situations.

switch (Type.GetTypeCode(someObject.GetType()))
{
    case TypeCode.Boolean:
        break;
    case TypeCode.Byte:
        break;
    case TypeCode.Char:
        break;
}

For custom types, you can create your own enumeration, and either an interface or a base class with abstract property or method...

Abstract class implementation of property

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes FooType { get; }
}
public class FooFighter : Foo
{
    public override FooTypes FooType { get { return FooTypes.FooFighter; } }
}

Abstract class implementation of method

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes GetFooType();
}
public class FooFighter : Foo
{
    public override FooTypes GetFooType() { return FooTypes.FooFighter; }
}

Interface implementation of property

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes FooType { get; }
}
public class FooFighter : IFooType
{
    public FooTypes FooType { get { return FooTypes.FooFighter; } }
}

Interface implementation of method

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes GetFooType();
}
public class FooFighter : IFooType
{
    public FooTypes GetFooType() { return FooTypes.FooFighter; }
}

One of my coworkers just told me about this too: This has the advantage that you can use it for literally any type of object, not just ones that you define. It has the disadvantage of being a bit larger and slower.

First define a static class like this:

public static class TypeEnumerator
{
    public class TypeEnumeratorException : Exception
    {
        public Type unknownType { get; private set; }
        public TypeEnumeratorException(Type unknownType) : base()
        {
            this.unknownType = unknownType;
        }
    }
    public enum TypeEnumeratorTypes { _int, _string, _Foo, _TcpClient, };
    private static Dictionary<Type, TypeEnumeratorTypes> typeDict;
    static TypeEnumerator()
    {
        typeDict = new Dictionary<Type, TypeEnumeratorTypes>();
        typeDict[typeof(int)] = TypeEnumeratorTypes._int;
        typeDict[typeof(string)] = TypeEnumeratorTypes._string;
        typeDict[typeof(Foo)] = TypeEnumeratorTypes._Foo;
        typeDict[typeof(System.Net.Sockets.TcpClient)] = TypeEnumeratorTypes._TcpClient;
    }
    /// <summary>
    /// Throws NullReferenceException and TypeEnumeratorException</summary>
    /// <exception cref="System.NullReferenceException">NullReferenceException</exception>
    /// <exception cref="MyProject.TypeEnumerator.TypeEnumeratorException">TypeEnumeratorException</exception>
    public static TypeEnumeratorTypes EnumerateType(object theObject)
    {
        try
        {
            return typeDict[theObject.GetType()];
        }
        catch (KeyNotFoundException)
        {
            throw new TypeEnumeratorException(theObject.GetType());
        }
    }
}

And then you can use it like this:

switch (TypeEnumerator.EnumerateType(someObject))
{
    case TypeEnumerator.TypeEnumeratorTypes._int:
        break;
    case TypeEnumerator.TypeEnumeratorTypes._string:
        break;
}
Toxicology answered 15/11, 2013 at 19:45 Comment(1)
Thanks for adding the TypeCode()-variant for primitive types, because even the C# 7.0 - variant does not work with those (neither does nameof() obviously)Hallmark
Q
6

I liked Virtlink's use of implicit typing to make the switch much more readable, but I didn't like that an early-out isn't possible, and that we're doing allocations. Let's turn up the perf a little.

public static class TypeSwitch
{
    public static void On<TV, T1>(TV value, Action<T1> action1)
        where T1 : TV
    {
        if (value is T1) action1((T1)value);
    }

    public static void On<TV, T1, T2>(TV value, Action<T1> action1, Action<T2> action2)
        where T1 : TV where T2 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
    }

    public static void On<TV, T1, T2, T3>(TV value, Action<T1> action1, Action<T2> action2, Action<T3> action3)
        where T1 : TV where T2 : TV where T3 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
        else if (value is T3) action3((T3)value);
    }

    // ... etc.
}

Well, that makes my fingers hurt. Let's do it in T4:

<#@ template debug="false" hostSpecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ import namespace="System.Linq" #> 
<#@ import namespace="System.IO" #> 
<#
    string GenWarning = "// THIS FILE IS GENERATED FROM " + Path.GetFileName(Host.TemplateFile) + " - ANY HAND EDITS WILL BE LOST!";
    const int MaxCases = 15;
#>
<#=GenWarning#>

using System;

public static class TypeSwitch
{
<# for(int icase = 1; icase <= MaxCases; ++icase) {
    var types = string.Join(", ", Enumerable.Range(1, icase).Select(i => "T" + i));
    var actions = string.Join(", ", Enumerable.Range(1, icase).Select(i => string.Format("Action<T{0}> action{0}", i)));
    var wheres = string.Join(" ", Enumerable.Range(1, icase).Select(i => string.Format("where T{0} : TV", i)));
#>
    <#=GenWarning#>

    public static void On<TV, <#=types#>>(TV value, <#=actions#>)
        <#=wheres#>
    {
        if (value is T1) action1((T1)value);
<# for(int i = 2; i <= icase; ++i) { #>
        else if (value is T<#=i#>) action<#=i#>((T<#=i#>)value);
<#}#>
    }

<#}#>
    <#=GenWarning#>
}

Adjusting Virtlink's example a little:

TypeSwitch.On(operand,
    (C x) => name = x.FullName,
    (B x) => name = x.LongName,
    (A x) => name = x.Name,
    (X x) => name = x.ToString(CultureInfo.CurrentCulture),
    (Y x) => name = x.GetIdentifier(),
    (object x) => name = x.ToString());

Readable and fast. Now, as everybody keeps pointing out in their answers, and given the nature of this question, order is important in the type matching. Therefore:

  • Put leaf types first, base types later.
  • For peer types, put more likely matches first to maximize perf.
  • This implies that there is no need for a special default case. Instead, just use the base-most type in the lambda, and put it last.
Quito answered 18/6, 2013 at 15:59 Comment(0)
E
5

Given inheritance facilitates an object to be recognized as more than one type, I think a switch could lead to bad ambiguity. For example:

Case 1

{
  string s = "a";
  if (s is string) Print("Foo");
  else if (s is object) Print("Bar");
}

Case 2

{
  string s = "a";
  if (s is object) Print("Foo");
  else if (s is string) Print("Bar");
}

Because s is a string and an object. I think when you write a switch(foo) you expect foo to match one and only one of the case statements. With a switch on types, the order in which you write your case statements could possibly change the result of the whole switch statement. I think that would be wrong.

You could think of a compiler-check on the types of a "typeswitch" statement, checking that the enumerated types do not inherit from each other. That doesn't exist though.

foo is T is not the same as foo.GetType() == typeof(T)!!

Eclosion answered 6/5, 2011 at 11:56 Comment(0)
P
5

As per C# 7.0 specification, you can declare a local variable scoped in a case of a switch:

object a = "Hello world";
switch (a)
{
    case string myString:
        // The variable 'a' is a string!
        break;
    case int myInt:
        // The variable 'a' is an int!
        break;
    case Foo myFoo:
        // The variable 'a' is of type Foo!
        break;
}

This is the best way to do such a thing because it involves just casting and push-on-the-stack operations, which are among the fastest operations an interpreter can run, just preceded by bitwise operations and boolean conditions.

As compared to using Dictionary<K, V>, here's much less memory usage and basically zero computation.

This, on the other hand, should be as fast (if not faster, even) as using a chain of if statements:

object a = "Hello world";
if (a is string)
{
    // The variable 'a' is a string!
} else if (a is int)
{
    // The variable 'a' is an int!
} // etc.
Pimentel answered 12/9, 2018 at 14:51 Comment(0)
P
5

Should work with

case type _:

like:

int i = 1;
bool b = true;
double d = 1.1;
object o = i; // whatever you want

switch (o)
{
    case int _:
        Answer.Content = "You got the int";
        break;
    case double _:
        Answer.Content = "You got the double";
        break;
    case bool _:
        Answer.Content = "You got the bool";
        break;
}
Pedantry answered 4/4, 2019 at 12:56 Comment(1)
if o is already System.Type ?Purport
P
4

I would either

Pitman answered 18/11, 2008 at 15:12 Comment(0)
K
4

Another way would be to define an interface IThing and then implement it in both classes here's the snipet:

public interface IThing
{
    void Move();
}

public class ThingA : IThing
{
    public void Move()
    {
        Hop();
    }

    public void Hop(){  
        //Implementation of Hop 
    }

}

public class ThingA : IThing
{
    public void Move()
    {
        Skip();
    }

    public void Skip(){ 
        //Implementation of Skip    
    }

}

public class Foo
{
    static void Main(String[] args)
    {

    }

    private void Foo(IThing a)
    {
        a.Move();
    }
}
Kiernan answered 18/11, 2008 at 15:57 Comment(0)
T
3

You can create overloaded methods:

void Foo(A a) 
{ 
    a.Hop(); 
}

void Foo(B b) 
{ 
    b.Skip(); 
}

void Foo(object o) 
{ 
    throw new ArgumentException("Unexpected type: " + o.GetType()); 
}

And cast the argument to dynamic type in order to bypass static type checking:

Foo((dynamic)something);
Tanika answered 4/1, 2013 at 12:30 Comment(0)
D
3

If you know the class you are expecting but you still don't have an object you can even do this:

private string GetAcceptButtonText<T>() where T : BaseClass, new()
{
    switch (new T())
    {
        case BaseClassReview _: return "Review";
        case BaseClassValidate _: return "Validate";
        case BaseClassAcknowledge _: return "Acknowledge";
        default: return "Accept";
    }
}
Durr answered 8/4, 2019 at 12:42 Comment(0)
S
2

I such cases I usually end up with a list of predicates and actions. Something along these lines:

class Mine {
    static List<Func<object, bool>> predicates;
    static List<Action<object>> actions;

    static Mine() {
        AddAction<A>(o => o.Hop());
        AddAction<B>(o => o.Skip());
    }

    static void AddAction<T>(Action<T> action) {
        predicates.Add(o => o is T);
        actions.Add(o => action((T)o);
    }

    static void RunAction(object o) {
        for (int i=0; o < predicates.Count; i++) {
            if (predicates[i](o)) {
                actions[i](o);
                break;
            }
        }
    }

    void Foo(object o) {
        RunAction(o);
    }
}
Selfregulated answered 18/11, 2008 at 15:14 Comment(0)
S
2

Create an interface IFooable, then make your A and B classes to implement a common method, which in turn calls the corresponding method you want:

interface IFooable
{
    public void Foo();
}

class A : IFooable
{
    //other methods ...

    public void Foo()
    {
        this.Hop();
    }
}

class B : IFooable
{
    //other methods ...

    public void Foo()
    {
        this.Skip();
    }
}

class ProcessingClass
{
    public void Foo(object o)
    {
        if (o == null)
            throw new NullRefferenceException("Null reference", "o");

        IFooable f = o as IFooable;
        if (f != null)
        {
            f.Foo();
        }
        else
        {
            throw new ArgumentException("Unexpected type: " + o.GetType());
        }
    }
}

Note, that it's better to use as instead first checking with is and then casting, as that way you make 2 casts, so it's more expensive.

Stevestevedore answered 18/11, 2008 at 15:26 Comment(0)
M
2

You're looking for Discriminated Unions which are a language feature of F#, but you can achieve a similar effect by using a library I made, called OneOf

https://github.com/mcintyre321/OneOf

The major advantage over switch (and if and exceptions as control flow) is that it is compile-time safe - there is no default handler or fall through

void Foo(OneOf<A, B> o)
{
    o.Switch(
        a => a.Hop(),
        b => b.Skip()
    );
}

If you add a third item to o, you'll get a compiler error as you have to add a handler Func inside the switch call.

You can also do a .Match which returns a value, rather than executes a statement:

double Area(OneOf<Square, Circle> o)
{
    return o.Match(
        square => square.Length * square.Length,
        circle => Math.PI * circle.Radius * circle.Radius
    );
}
Middlesworth answered 2/8, 2017 at 8:52 Comment(0)
D
2

I would create an interface with whatever name and method name that would make sense for your switch, let's call them respectively: IDoable that tells to implement void Do().

public interface IDoable
{
    void Do();
}

public class A : IDoable
{
    public void Hop() 
    {
        // ...
    }

    public void Do()
    {
        Hop();
    }
}

public class B : IDoable
{
    public void Skip() 
    {
        // ...
    }

    public void Do()
    {
        Skip();
    }
}

and change the method as follows:

void Foo<T>(T obj)
    where T : IDoable
{
    // ...
    obj.Do();
    // ...
}

At least with that you are safe at the compilation-time and I suspect that performance-wise it's better than checking type at runtime.

Damle answered 8/11, 2018 at 17:19 Comment(0)
K
2

With C# 8 onwards you can make it even more concise with the new switch. And with the use of discard option _ you can avoid creating innecesary variables when you don't need them, like this:

        return document switch {
            Invoice _ => "Is Invoice",
            ShippingList _ => "Is Shipping List",
            _ => "Unknown"
        };

Invoice and ShippingList are classes and document is an object that can be either of them.

Kabuki answered 25/2, 2020 at 2:8 Comment(1)
Note: must use _ (or variable name), otherwise it won't compile. Easy to miss.Aqua
A
1

After having compared the options a few answers here provided to F# features, I discovered F# to have a way better support for type-based switching (although I'm still sticking to C#).
You might want to see here and here.

Anthropolatry answered 18/11, 2008 at 15:22 Comment(0)
B
1

Try to go that way:

public void Test(BaseType @base)
{
    switch (@base)
    {
        case ConcreteType concrete:
            DoSomething(concrete);
            break;

        case AnotherConcrete concrete:
            DoSomething(concrete);
            break;
    }
}
Bute answered 14/7, 2020 at 11:25 Comment(0)
H
0

I agree with Jon about having a hash of actions to class name. If you keep your pattern, you might want to consider using the "as" construct instead:

A a = o as A;
if (a != null) {
    a.Hop();
    return;
}
B b = o as B;
if (b != null) {
    b.Skip();
    return;
}
throw new ArgumentException("...");

The difference is that when you use the patter if (foo is Bar) { ((Bar)foo).Action(); } you're doing the type casting twice. Now maybe the compiler will optimize and only do that work once - but I wouldn't count on it.

Horoscope answered 18/11, 2008 at 15:23 Comment(1)
I really not like multiple exit points (returns), but if you want to stick with this, add "if (o == null) throw" in the beginning, as later on you will not know if the cast is unsuccessful, or the object was null.Stevestevedore
D
0

As Pablo suggests, interface approach is almost always the right thing to do to handle this. To really utilize switch, another alternative is to have a custom enum denoting your type in your classes.

enum ObjectType { A, B, Default }

interface IIdentifiable
{
    ObjectType Type { get; };
}
class A : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.A; } }
}

class B : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.B; } }
}

void Foo(IIdentifiable o)
{
    switch (o.Type)
    {
        case ObjectType.A:
        case ObjectType.B:
        //......
    }
}

This is kind of implemented in BCL too. One example is MemberInfo.MemberTypes, another is GetTypeCode for primitive types, like:

void Foo(object o)
{
    switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
    {
        case TypeCode.Int16:
        case TypeCode.Int32:
        //etc ......
    }
}
Dogtooth answered 3/1, 2013 at 3:44 Comment(0)
M
0

This is an alternate answer that mixes contributions from JaredPar and VirtLink answers, with the following constraints:

  • The switch construction behaves as a function, and receives functions as parameters to cases.
  • Ensures that it is properly built, and there always exists a default function.
  • It returns after first match (true for JaredPar answer, not true for VirtLink one).

Usage:

 var result = 
   TSwitch<string>
     .On(val)
     .Case((string x) => "is a string")
     .Case((long x) => "is a long")
     .Default(_ => "what is it?");

Code:

public class TSwitch<TResult>
{
    class CaseInfo<T>
    {
        public Type Target { get; set; }
        public Func<object, T> Func { get; set; }
    }

    private object _source;
    private List<CaseInfo<TResult>> _cases;

    public static TSwitch<TResult> On(object source)
    {
        return new TSwitch<TResult> { 
            _source = source,
            _cases = new List<CaseInfo<TResult>>()
        };
    }

    public TResult Default(Func<object, TResult> defaultFunc)
    {
        var srcType = _source.GetType();
       foreach (var entry in _cases)
            if (entry.Target.IsAssignableFrom(srcType))
                return entry.Func(_source);

        return defaultFunc(_source);
    }

    public TSwitch<TResult> Case<TSource>(Func<TSource, TResult> func)
    {
        _cases.Add(new CaseInfo<TResult>
        {
            Func = x => func((TSource)x),
            Target = typeof(TSource)
        });
        return this;
    }
}
Mccomb answered 23/8, 2016 at 10:59 Comment(0)
M
0

Yes - just use the slightly weirdly named "pattern matching" from C#7 upwards to match on class or structure:

IObject concrete1 = new ObjectImplementation1();
IObject concrete2 = new ObjectImplementation2();

switch (concrete1)
{
    case ObjectImplementation1 c1: return "type 1";         
    case ObjectImplementation2 c2: return "type 2";         
}
Mercantile answered 29/10, 2018 at 12:5 Comment(0)
E
0

I use

    public T Store<T>()
    {
        Type t = typeof(T);

        if (t == typeof(CategoryDataStore))
            return (T)DependencyService.Get<IDataStore<ItemCategory>>();
        else
            return default(T);
    }
Elli answered 28/11, 2018 at 23:4 Comment(0)
B
0

switch-case-type pattern is one that indicates the need for inversion of control.

switch (a)
{
  case type1 t:
        Action1(t);
        break;
  case type2 t:
        Action2(t);
        break;
  case type3 t:
        Action3(t);
        break;
}

is suggesting that type1, type2, type3 share the same interface method, let us call it Action. This interface can be added to type1, type2, type3 and then Action1, Action2, Action3 should go as implementation of this method and then there is no switch, everything becomes

a.Action();

and that is it.

Bates answered 19/6, 2023 at 9:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.