本文翻譯自: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;
}