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

后端 未结 30 2512
梦毁少年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:06

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

    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.

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

    Switching on types is definitely lacking in C# (UPDATE: in C#7 / VS 2017 switching on types is supported - see Zachary Yates's answer below). 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
            };
        }
    }
    
    0 讨论(0)
  • 2020-11-22 04:09

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

    I would either

    • use method overloading (just like x0n), or
    • use subclasses (just like Pablo), or
    • apply the visitor pattern.
    0 讨论(0)
  • 2020-11-22 04:09

    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.

    0 讨论(0)
提交回复
热议问题