Is there a better alternative than this to 'sw

2018-12-31 07:15发布

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

25条回答
闭嘴吧你
2楼-- · 2018-12-31 07:26

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);
        }
    }
}
查看更多
高级女魔头
3楼-- · 2018-12-31 07:27

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.

查看更多
墨雨无痕
4楼-- · 2018-12-31 07:27

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.

查看更多
几人难应
5楼-- · 2018-12-31 07:31

Yes thank to C#7 this 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());
        }
查看更多
孤独总比滥情好
6楼-- · 2018-12-31 07:31

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 use dynamic parameter type to bypass static type checking:

Foo((dynamic)something);
查看更多
余生无你
7楼-- · 2018-12-31 07:32

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.

查看更多
登录 后发表回答