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

后端 未结 30 2404
梦毁少年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 03:56

    You can use pattern matching in C# 7 or above:

    switch (foo.GetType())
    {
        case var type when type == typeof(Player):
            break;
        case var type when type == typeof(Address):
            break;
        case var type when type == typeof(Department):
            break;
        case var type when type == typeof(ContactType):
            break;
        default:
            break;
    }
    
    0 讨论(0)
  • 2020-11-22 03:56

    As per C# 7.0 specification, you can declare a local variable scoped in a case of a switch:

    object a = "Hello world";
    switch (a)
    {
        case string myString:
            // The variable 'a' is a string!
            break;
        case int myInt:
            // The variable 'a' is an int!
            break;
        case Foo myFoo:
            // The variable 'a' is of type Foo!
            break;
    }
    

    This is the best way to do such a thing because it involves just casting and push-on-the-stack operations, which are the fastest operations an interpreter can run just after bitwise operations and boolean conditions.

    Comparing this to a Dictionary<K, V>, here's much less memory usage: holding a dictionary requires more space in the RAM and some computation more by the CPU for creating two arrays (one for keys and the other for values) and gathering hash codes for the keys to put values to their respective keys.

    So, for as far I know, I don't believe that a faster way could exist unless you want to use just an if-then-else block with the is operator as follows:

    object a = "Hello world";
    if (a is string)
    {
        // The variable 'a' is a string!
    } else if (a is int)
    {
        // The variable 'a' is an int!
    } // etc.
    
    0 讨论(0)
  • 2020-11-22 03:56

    If you know the class you are expecting but you still don't have an object you can even do this:

    private string GetAcceptButtonText<T>() where T : BaseClass, new()
    {
        switch (new T())
        {
            case BaseClassReview _: return "Review";
            case BaseClassValidate _: return "Validate";
            case BaseClassAcknowledge _: return "Acknowledge";
            default: return "Accept";
        }
    }
    
    0 讨论(0)
  • 2020-11-22 03:57

    I would create an interface with whatever name and method name that would make sense for your switch, let's call them respectively: IDoable that tells to implement void Do().

    public interface IDoable
    {
        void Do();
    }
    
    public class A : IDoable
    {
        public void Hop() 
        {
            // ...
        }
    
        public void Do()
        {
            Hop();
        }
    }
    
    public class B : IDoable
    {
        public void Skip() 
        {
            // ...
        }
    
        public void Do()
        {
            Skip();
        }
    }
    

    and change the method as follows:

    void Foo<T>(T obj)
        where T : IDoable
    {
        // ...
        obj.Do();
        // ...
    }
    

    At least with that you are safe at the compilation-time and I suspect that performance-wise it's better than checking type at runtime.

    0 讨论(0)
  • 2020-11-22 03:59

    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.

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

    I liked Virtlink's use of implicit typing to make the switch much more readable, but I didn't like that an early-out isn't possible, and that we're doing allocations. Let's turn up the perf a little.

    public static class TypeSwitch
    {
        public static void On<TV, T1>(TV value, Action<T1> action1)
            where T1 : TV
        {
            if (value is T1) action1((T1)value);
        }
    
        public static void On<TV, T1, T2>(TV value, Action<T1> action1, Action<T2> action2)
            where T1 : TV where T2 : TV
        {
            if (value is T1) action1((T1)value);
            else if (value is T2) action2((T2)value);
        }
    
        public static void On<TV, T1, T2, T3>(TV value, Action<T1> action1, Action<T2> action2, Action<T3> action3)
            where T1 : TV where T2 : TV where T3 : TV
        {
            if (value is T1) action1((T1)value);
            else if (value is T2) action2((T2)value);
            else if (value is T3) action3((T3)value);
        }
    
        // ... etc.
    }
    

    Well, that makes my fingers hurt. Let's do it in T4:

    <#@ template debug="false" hostSpecific="true" language="C#" #>
    <#@ output extension=".cs" #>
    <#@ Assembly Name="System.Core.dll" #>
    <#@ import namespace="System.Linq" #> 
    <#@ import namespace="System.IO" #> 
    <#
        string GenWarning = "// THIS FILE IS GENERATED FROM " + Path.GetFileName(Host.TemplateFile) + " - ANY HAND EDITS WILL BE LOST!";
        const int MaxCases = 15;
    #>
    <#=GenWarning#>
    
    using System;
    
    public static class TypeSwitch
    {
    <# for(int icase = 1; icase <= MaxCases; ++icase) {
        var types = string.Join(", ", Enumerable.Range(1, icase).Select(i => "T" + i));
        var actions = string.Join(", ", Enumerable.Range(1, icase).Select(i => string.Format("Action<T{0}> action{0}", i)));
        var wheres = string.Join(" ", Enumerable.Range(1, icase).Select(i => string.Format("where T{0} : TV", i)));
    #>
        <#=GenWarning#>
    
        public static void On<TV, <#=types#>>(TV value, <#=actions#>)
            <#=wheres#>
        {
            if (value is T1) action1((T1)value);
    <# for(int i = 2; i <= icase; ++i) { #>
            else if (value is T<#=i#>) action<#=i#>((T<#=i#>)value);
    <#}#>
        }
    
    <#}#>
        <#=GenWarning#>
    }
    

    Adjusting Virtlink's example a little:

    TypeSwitch.On(operand,
        (C x) => name = x.FullName,
        (B x) => name = x.LongName,
        (A x) => name = x.Name,
        (X x) => name = x.ToString(CultureInfo.CurrentCulture),
        (Y x) => name = x.GetIdentifier(),
        (object x) => name = x.ToString());
    

    Readable and fast. Now, as everybody keeps pointing out in their answers, and given the nature of this question, order is important in the type matching. Therefore:

    • Put leaf types first, base types later.
    • For peer types, put more likely matches first to maximize perf.
    • This implies that there is no need for a special default case. Instead, just use the base-most type in the lambda, and put it last.
    0 讨论(0)
提交回复
热议问题