次の方法で共有


Switching on Types

One action I find frustrating in C# is where a particular action needs to be taken based off of the type of a particular object.  Ideally I would like to solve this with a switch statement but switch statements only support constant expressions in C# so no luck there.  Previously I've had to resort to ugly looking code like the following. 

 Type t = sender.GetType();
if (t == typeof(Button)) {
    var realObj = (Button)sender;
    // Do Something
}
else if (t == typeof(CheckBox)) {
    var realObj = (CheckBox)sender;
    // Do something else
}
else {
    // Default action
}

Yes I realize this isn't the ugliest code but it seems less elegant to me over a standard switch statement and I find the casting tedious.  Especially since it requires you to write every type twice. 

What I want to say is "Given this type, execute this block of code."  So I decided to run with that this afternoon.  Lambdas will serve nicely for the block of code and using type inference will allow us to avoid writing every type out twice.  What I ended up with was a class called TypeSwitch with 3 main methods (see bottom of post for full code).

  • Do - Entry point where switching begins
  • Case - Several overloads which take a generic type argument and an Action<T> to run for the object
  • Default - Optional default action. 

I wrote a quick winforms app to test out the solution.  I dropped some random controls on a from and bound the MouseHover event for all of them to a single method.  I can now use the following code to print out different messages based on the type. 

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

 

Notice that for check box I was able to access CheckBox properties in a strongly typed fashion.  The underlying case code will optionally pass the first argument to the lambda expression strongly typed to the value specified as the generic argument. 

There are a couple of faults with this approach including Default being anywhere in the list but it was a fun experiment and works well. 

TypeSwitch code.

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

Comments

  • Anonymous
    May 16, 2008
    You can have a version that uses IsSubclassOf instead of == so that for example the checkbox case fires for classes derived from checkbox. (And then the Default case becomes just IsSubclassOf(Object).)

  • Anonymous
    May 18, 2008
    Agreed.  This sample can definately be expanded upon for sub typing and potentially other items such as interface implementation.  

  • Anonymous
    May 19, 2008
    I guess my point was that some people who do 'switching on types' really do expect subclasses to match. Otherwise you get problems like "This code says that I have to pass a checkbox, and I do pass a checkbox, but it still throws a 'wrong type' exception!"

  • Anonymous
    May 20, 2008
    Having read Jared Parsons blog entry on Switching on Types , I was inspired to follow on with a class

  • Anonymous
    May 22, 2008
    I just did something similar today, I wrote an enum containing the type names I expected, and then parsed the name of the type as the enum and switched on the resultant enum value, roughly like this: enum Types { Button ... } ; Types typ = System.Enum.Parse ( typeof(Types) , sender.GetType().Name ) ; switch ( typ ) { case Types.Button : ...

  • Anonymous
    May 26, 2008
    Oh, please no! C# left this functionality out for a good reason.  Please let it stay out. In object-oriented programming, you should never care what kind of object you're working with.  Never check what an object type is and then take an action.  Instead, ask an object to do something, and the object will take the appropriate action. Especially now that you can add methods to existing classes, there should be no need for this type of code. Your example should read: textBox1.Text = sender.DefaultDescriptiveText; and each class should implement DefaultDescriptiveText appropriately, for example Button returns "Hit a Button".  If there is no more specific action, the base class would implement the default action. Not only is this cleaner, it maintains knowledge of what the sender should do in the sender's class rather than strewing it all over your project wherever the sender gets used.  With your implementation, what happens to all those places where you've got "Hit a Button" when you want to change it to "Click a button"?  Or, the real win, when change the code, and suddenly sender can now be a scrollbar how many places to I need to change the code? One -- in scrollbar.  With your method, I need to find every use of sender to check if scrollbars are handled correctly. Please, don't encourage use of type tests, they're contrary to well designed object-oriented programming.  For that matter, switch statements should largely be eliminated too. (I won't as far as some and suggest elimination of if statements though.) E.

  • Anonymous
    May 28, 2008
    Hey Silverhalide, In general I agree.  Making decisions based off of types is not the ideal approach.  Virtual method dispatch or interfaces are much more suitable. The problem with your solution though is it assumes that I have the ability to modify the types in question.  In this case they are BCL types and I do not have this ability.   I could subclass these types and add an interface.  This is not a general solution though because the type in question could be sealed.  In addition it's quite annoying to have to subclass multiple classes when I just want to call a single method based on the type of the object.  Doubly so when it will only be used in one particular method call in one object.  

  • Anonymous
    May 28, 2008
    Yes, this is more complicated if you can't alter the classes being used.  I could have sworn that adding methods to existing classes was added to C# in the last couple of years (maybe there's limits on it that slip my mind right now).  However, an alternative approach is to make use of parametric polymorphism. Your example would read: textBox1.Text = DefaultDescriptiveText.Generate(sender); and then the DefaultDescriptiveText class would implement Generate(CheckBox), Generate(Button), and so on.  Again this would be able to handle inheritance hierarchies, etc. that the original proposal couldn't deal with nicely.

  • Anonymous
    March 18, 2009
    I chose my career wisely.&#160; I can tell because at least once a week I run across a nifty small thing