views:

5549

answers:

12

Seeing as C# can't switch on a Type (which I gather wasn't added as a special case because is-a relationships mean that more than one distinct case might apply), is there a better way to simulate switching on type 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());
    }
}
+27  A: 

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.

Jon Skeet
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 Gravell
+1 for Marc's comment :)
Jon Skeet
@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.
Harper Shelby
I've used this technique alot in the past, usually before moving to an IoC Container
Chris Canal
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?
Robert Rossney
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.
Erik Forbes
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'.
Erik Forbes
Obviously some reflection would be required. =P
Erik Forbes
+11  A: 

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.

Pablo Fernandez
This only works if A and B are "custom" types ...
bruno conde
True bruno, but the question doesn't suggest that. You could include that in your answer though Pablo.
Dana the Sane
From the question I think A and B are generic enough that they can be A = String ; B = List<int> for instance ...
bruno conde
+6  A: 

You could switch on the type name

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

Just a thought, hope it helps!

Zachary Yates
+1  A: 

I would either

Jonas Kongslund
+1  A: 

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);
  }
}
Hallgrim
+8  A: 

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.

sep332
+1  A: 

I looked at a few options here, mirroring what F# can do. F# has much better support for type-based switching (although I'm still sticking to C# ;-p). You might want to see here and here.

Marc Gravell
A: 

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.

plinth
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.
Sunny
+1  A: 

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 (expensive).

Sunny
+17  A: 

Switching on types is definately lacking in C#. 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.

http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx

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 || type == entry.Target) {
                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
        };
    }
JaredPar
"type == entry.Target" can also be changed to "entry.Target.IsAssignableFrom(type)" to take compatible types (e.g., subclasses) into account.
Mark Cidade
@marxidad: Excellent point.
Zachary Yates
+1. This great and solves a real problem for me!
Marcel
+2  A: 

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();
    }
}
jgarcia
+1  A: 

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.

Paul Batum