“開啓類型”還有比這更好的選擇嗎?

本文翻譯自:Is there a better alternative than this to 'switch on type'?

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? 看作C#無法打開一個Type(我收集的並不是作爲特殊情況添加的,因爲is-a關係意味着可能有多個不同的情況可能適用),是否有更好的方法來模擬切換類型?

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

#1樓

參考:https://stackoom.com/question/1FmC/開啓類型-還有比這更好的選擇嗎


#2樓

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: 有了JaredPar的回答我寫了一個他的TypeSwitch類的變體,它使用類型推斷來獲得更好的語法:

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. 請注意, Case()方法的順序很重要。


Get the full and commented code for my TypeSwitch class . 獲取我的TypeSwitch類的完整和註釋代碼 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樓

As Pablo suggests, interface approach is almost always the right thing to do to handle this. 正如Pablo所說,接口方法幾乎總是正確的做法來處理這個問題。 To really utilize switch, another alternative is to have a custom enum denoting your type in your classes. 要真正利用switch,另一種方法是在類中使用自定義枚舉來表示類型。

enum ObjectType { A, B, Default }

interface IIdentifiable
{
    ObjectType Type { get; };
}
class A : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.A; } }
}

class B : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.B; } }
}

void Foo(IIdentifiable o)
{
    switch (o.Type)
    {
        case ObjectType.A:
        case ObjectType.B:
        //......
    }
}

This is kind of implemented in BCL too. 這也是在BCL中實現的。 One example is MemberInfo.MemberTypes , another is GetTypeCode for primitive types, like: 一個例子是MemberInfo.MemberTypes ,另一個例子是原始類型的GetTypeCode ,例如:

void Foo(object o)
{
    switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
    {
        case TypeCode.Int16:
        case TypeCode.Int32:
        //etc ......
    }
}

#4樓

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 cast the argument to dynamic type in order to bypass static type checking: 並將參數轉換爲dynamic類型以繞過靜態類型檢查:

Foo((dynamic)something);

#5樓

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. 我喜歡Virtlink 使用隱式類型來使交換機更具可讀性,但我不喜歡早期不可能,而且我們正在進行分配。 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: 我們在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: 稍微調整Virtlink的示例:

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. 對於對等類型,首先放置更多可能的匹配以最大化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. 相反,只需使用lambda中最基本的類型,並將其放在最後。

#6樓

For built-in types, you can use the TypeCode enumeration. 對於內置類型,您可以使用TypeCode枚舉。 Please note that GetType() is kind of slow, but probably not relevant in most situations. 請注意,GetType()有點慢,但在大多數情況下可能不相關。

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章