ChatGPT解决这个技术问题 Extra ChatGPT

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

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

S
Shimmy Weitzhandler

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

I don't like this answer because nameof(NamespaceA.ClassC) == nameof(NamespaceB.ClassC) is true.
(c# 7) you can also use underscore if you don't need access to the object: case UnauthorizedException _:
@ischas, that is a very important point. Thank you! However using nameof is better than ToString because of the compiler protection.
B
Bill Tür stands with Ukraine

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://docs.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
        };
    }
}

"type == entry.Target" can also be changed to "entry.Target.IsAssignableFrom(type)" to take compatible types (e.g., subclasses) into account.
Altered the code to use "entry.Target.IsAssignableFrom(type)" so that subclasses are supported.
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)
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.
J
Jon Skeet

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.


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 +1
@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.
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?
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.
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'.
C
Community

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

@Virtlink Thanks for this static helper, I made a small fork of your gist for compatibility with Silverlight (Windows Phone) and this.value readonly.
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) ...
Also, I think you can simply say value is TTarget instead of all that IsAssignableFrom stuff.
@JoeyAdams: I incorporated your last suggestion, along with some small improvements. However, I'm keeping the syntax the same.
@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.
D
Davide Cannizzo

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

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)):
You are doing it wrong. See the answer by Serge Intern and read about Liskov substitution principle
P
Pablo Fernandez

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.


True bruno, but the question doesn't suggest that. You could include that in your answer though Pablo.
From the question I think A and B are generic enough that they can be A = String ; B = List for instance ...
P
PilgrimViis

C# 8 enhancements of pattern matching made it possible to do it like this. In some cases it do the job and more concise.

        public Animal Animal { get; set; }
        ...
        var animalName = Animal switch
        {
            Cat cat => "Tom",
            Mouse mouse => "Jerry",
            _ => "unknown"
        };

you do not need the identifier if it's unused. but otherwise, this should be the top answer now.
D
Davide Cannizzo

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

P
Paul Batum

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.


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.
s
sep332

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.


Overload resolution is determined statically so that just won't work at all.
@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.
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.
@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).
E
Edward Ned Harvey

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

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)
C
Community

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.


o
one.beat.consumer

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)!!


C
Community

I would either

use method overloading (just like x0n), or

use subclasses (just like Pablo), or

apply the visitor pattern.


j
jgarcia

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

D
Davide Cannizzo

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 the fastest operations an interpreter can run just after bitwise operations and boolean conditions.

Comparing this to a Dictionary<K, V>, here's much less memory usage: holding a dictionary requires more space in the RAM and some computation more by the CPU for creating two arrays (one for keys and the other for values) and gathering hash codes for the keys to put values to their respective keys.

So, for as far I know, I don't believe that a faster way could exist unless you want to use just an if-then-else block with the is operator as follows:

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.

T
TylerH

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

D
Davide Cannizzo

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

m
mcintyre321

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

C
Chan

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

D
Davide Cannizzo

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.


D
Davide Cannizzo

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

D
Desmond

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.


Note: must use _ (or variable name), otherwise it won't compile. Easy to miss.
N
Natalie Perret

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.


D
Davide Cannizzo

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.


p
pablocom96

Try to go that way:

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

        case AnotherConcrete concrete:
            DoSomething(concrete);
            break;
    }
}

p
plinth

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.


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.
C
Community

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 ......
    }
}

j
jruizaranguren

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

J
James Harcourt

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