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

后端 未结 30 3028
梦毁少年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条回答
  •  猫巷女王i
    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 value, Action action1)
            where T1 : TV
        {
            if (value is T1) action1((T1)value);
        }
    
        public static void On(TV value, Action action1, Action 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 value, Action action1, Action action2, Action 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 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 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.

提交回复
热议问题