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

后端 未结 30 2406
梦毁少年i
梦毁少年i 2020-11-22 03:28

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

相关标签:
30条回答
  • 2020-11-22 04:10

    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.

    0 讨论(0)
  • 2020-11-22 04:10

    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;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 04:11

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 04: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());
    }
    
    0 讨论(0)
  • 2020-11-22 04:12

    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.

    0 讨论(0)
  • 2020-11-22 04:13

    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);
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题