有没有比这更好的替代“打开types”?
看作是C#不能切换一个types(我收集的没有作为特殊情况添加,因为是一个关系意味着不止一个不同的情况可能适用),是否有更好的方式来模拟types切换比这个?
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()); } }
C#(更新:在C#7 / VS 2017切换types支持 – 请参阅下面的答案 )切换types肯定是缺乏。 为了做到这一点,没有一个大的if / else if / else语句,你需要使用不同的结构。 我写了一篇博客文章,详细介绍了如何构buildTypeSwitch结构。
http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx
简短版本:TypeSwitch旨在防止冗余转换,并提供类似于正常开关/ case语句的语法。 例如,这里是TypeSwitch在标准Windows窗体事件上的操作
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"));
TypeSwitch的代码实际上很小,可以很容易地放到你的项目中。
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 }; } }
使用 Visual Studio 2017(Release 15. *)附带的C#7 ,您可以在case
语句中使用Types:
switch(shape) { case Circle c: WriteLine($"circle with radius {c.Radius}"); break; case Rectangle s when (s.Length == s.Height): WriteLine($"{s.Length} x {s.Height} square"); break; case Rectangle r: WriteLine($"{r.Length} x {r.Height} rectangle"); break; default: WriteLine("<unknown shape>"); break; case null: throw new ArgumentNullException(nameof(shape)); }
在C#6中,可以使用带有nameof()运算符的switch语句(谢谢@Joey Adams):
switch(o.GetType().Name) { case nameof(AType): break; case nameof(BType): break; }
使用C#5和更早的版本,你可以使用switch语句,但是你将不得不使用一个包含types名称的魔术string…这不是特别的重构友好(谢谢@ nukefusion)
switch(o.GetType().Name) { case "AType": break; }
一种select是从“ Type
到Action
(或其他某个委托)中具有字典。 根据types查找动作,然后执行。 在此之前我已经把这个用于工厂了。
在JaredPar的回答中, 我写了一个TypeSwitch
类的变体,它使用types推断来获得更好的语法:
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; }
请注意, Case()
方法的顺序很重要。
获取我的TypeSwitch
类的完整和评论的代码 。 这是一个工作缩写版本:
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); } } }
创build一个超类(S)并使A和Binheritance它。 然后在S上声明每个子类需要实现的抽象方法。
做这个“foo”方法也可以把它的签名改成Foo(S o),这样就可以保证types的安全,而且不需要抛出那个丑陋的exception。
你应该真的超负荷你的方法,而不是自己消除歧义。 到目前为止,大多数的答案都没有考虑到未来的子类,这可能会在以后导致非常糟糕的维护问题。
如果您使用C#4,则可以使用新的dynamicfunction来实现一个有趣的select。 我并不是说这样比较好,实际上它很可能会慢一些,但它确实有一定的优雅。
class Thing { void Foo(A a) { a.Hop(); } void Foo(B b) { b.Skip(); } }
用法:
object aOrB = Get_AOrB(); Thing t = GetThing(); ((dynamic)t).Foo(aorB);
这个工作的原因是C#4dynamic方法调用的重载在运行时而不是编译时被解决。 最近我写了一些关于这个想法的文章。 再次,我只想重申,这可能比其他所有的build议都要糟糕,我只是把它当作好奇而提供。
我喜欢Virtlink 使用隐式types来使开关更具可读性,但是我不喜欢这样做,因为我们不能进行早期的分配。 让我们来看一下这个表演。
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. }
那么,这让我的手指受伤。 我们在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#> }
稍微调整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());
可读性和快速。 现在,正如大家一直在回答中指出的那样,考虑到这个问题的性质,顺序在types匹配中很重要。 因此:
- 先放叶型,后放底座型。
- 对于同伴types,首先放置更可能的匹配来最大化性能。
- 这意味着不需要特殊的默认情况。 相反,只需使用lambda中最基本的types,并将其放在最后。
鉴于inheritance有利于一个对象被认为是不止一种types,我认为转换可能会导致模糊不清。 例如:
情况1
{ string s = "a"; if (s is string) Print("Foo"); else if (s is object) Print("Bar"); }
案例2
{ string s = "a"; if (s is object) Print("Foo"); else if (s is string) Print("Bar"); }
因为s是一个string和一个对象。 我想当你写一个switch(foo)
你希望foo只匹配一个case
语句。 随着types的切换,写入case语句的顺序可能会改变整个switch语句的结果。 我认为这是错的。
你可以想象一个编译器 – 检查“typeswitch”语句的types,检查枚举types是否不相互inheritance。 这并不存在。
foo is T
不是一样的foo.GetType() == typeof(T)
!!
我也是
- 使用方法重载(就像x0n ),或者
- 使用子类(就像Pablo ),或者
- 应用访问者模式 。
对于内置types,您可以使用TypeCode枚举。 请注意,GetType()有点慢,但在大多数情况下可能不相关。
switch (Type.GetTypeCode(someObject.GetType())) { case TypeCode.Boolean: break; case TypeCode.Byte: break; case TypeCode.Char: break; }
对于自定义types,您可以创build自己的枚举,以及具有抽象属性或方法的接口或基类…
属性的抽象类实现
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; } } }
抽象类的实现方法
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; } }
财产的接口实现
public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu }; public interface IFooType { FooTypes FooType { get; } } public class FooFighter : IFooType { public FooTypes FooType { get { return FooTypes.FooFighter; } } }
方法的接口实现
public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu }; public interface IFooType { FooTypes GetFooType(); } public class FooFighter : IFooType { public FooTypes GetFooType() { return FooTypes.FooFighter; } }
我的同事之一也告诉过我这个:这样做的好处是你可以把它用于任何types的对象,而不仅仅是你定义的对象。 它有一个更大,更慢的缺点。
首先定义一个这样的静态类:
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()); } } }
然后你可以像这样使用它:
switch (TypeEnumerator.EnumerateType(someObject)) { case TypeEnumerator.TypeEnumeratorTypes._int: break; case TypeEnumerator.TypeEnumeratorTypes._string: break; }
我在这里看了几个选项,反映了F#可以做的事情。 F#对基于types的切换有更好的支持(尽pipe我仍然坚持C#; -p)。 你可能想看看这里和这里 。
是的感谢C#7这可以实现,这是如何做(使用expression式 ):
switch(o) { case A a: a.Hop(); break; case B b: b.Skip(); break; case C _: return new ArgumentException("Type C will be supported in the next version"); default: return new ArgumentException("Unexpected type: " + o.GetType()); }
在这种情况下,我通常会得到一个谓词和动作列表。 沿着这些线路的东西:
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); } }
创build一个IFooable接口,然后让你的A和B类实现一个通用的方法,然后调用你想要的相应的方法:
interface IFooable { public void Foo(); } class A : IFooable { //other methods ... public void Foo() { this.Hop(); } } class B : IFooable { //other methods ... public void Foo() { this.Skip(); } } class ProcessingClass { public void Foo(object o) { if (o == null) throw new NullRefferenceException("Null reference", "o"); IFooable f = o as IFooable; if (f != null) { f.Foo(); } else { throw new ArgumentException("Unexpected type: " + o.GetType()); } } }
请注意,最好使用“as”,而不是先用“is”来检查,然后再进行施法,就像这样做2次施法(昂贵的)。
另一种方法是定义一个接口IThing,然后在这两个类中实现它snipet:
public interface IThing { void Move(); } public class ThingA : IThing { public void Move() { Hop(); } public void Hop(){ //Implementation of Hop } } public class ThingA : IThing { public void Move() { Skip(); } public void Skip(){ //Implementation of Skip } } public class Foo { static void Main(String[] args) { } private void Foo(IThing a) { a.Move(); } }
您可以创build重载的方法:
void Foo(A a) { a.Hop(); } void Foo(B b) { b.Skip(); } void Foo(object o) { throw new ArgumentException("Unexpected type: " + o.GetType()); }
并使用dynamic参数types来绕过静态types检查:
Foo((dynamic)something);
您正在寻找Discriminated Unions
,这是F#的语言function,但您可以通过使用我制作的名为OneOf的库实现类似的效果
https://github.com/mcintyre321/OneOf
switch
的主要优点(以及if
和exceptions as control flow
)是编译时安全 – 没有默认处理程序或通过
void Foo(OneOf<A, B> o) { o.Switch( a => a.Hop(), b => b.Skip() ); }
如果你添加第三个项目到o,你会得到一个编译器错误,因为你必须在交换机调用中添加一个处理函数Func。
你也可以做一个.Match
,它返回一个值,而不是执行一个语句:
double Area(OneOf<Square, Circle> o) { return o.Match( square => square.Length * square.Length, circle => Math.PI * circle.Radius * circle.Radius ); }
我同意乔恩关于类名的行为散列。 如果你保持你的模式,你可能要考虑使用“as”构造:
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("...");
不同的是,当你使用模式if(foo是Bar){((Bar)foo).Action(); }你正在做types转换两次。 现在,编译器可能会优化,只做一次 – 但我不会指望它。
正如Pablo所说,接口方法几乎总是正确的。 要真正地利用开关,另一个select是在你的类中有一个自定义的枚举types。
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: //...... } }
BCL也是这样实施的。 一个例子是MemberInfo.MemberTypes ,另一个是基本types的GetTypeCode
,例如:
void Foo(object o) { switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode() { case TypeCode.Int16: case TypeCode.Int32: //etc ...... } }
这是将JaredPar和VirtLink答案的贡献混合在一起的替代答案,具有以下限制:
- 开关结构performance为一种function ,并接受作为参数的情况下的function 。
- 确保正确构build,并且总是存在默认function 。
- 它在第一次匹配之后返回 (对于JaredPar回答为true,对于VirtLink回答为true)。
用法:
var result = TSwitch<string> .On(val) .Case((string x) => "is a string") .Case((long x) => "is a long") .Default(_ => "what is it?");
码:
public class TSwitch<TResult> { class CaseInfo<T> { public Type Target { get; set; } public Func<object, T> Func { get; set; } } private object _source; private List<CaseInfo<TResult>> _cases; public static TSwitch<TResult> On(object source) { return new TSwitch<TResult> { _source = source, _cases = new List<CaseInfo<TResult>>() }; } public TResult Default(Func<object, TResult> defaultFunc) { var srcType = _source.GetType(); foreach (var entry in _cases) if (entry.Target.IsAssignableFrom(srcType)) return entry.Func(_source); return defaultFunc(_source); } public TSwitch<TResult> Case<TSource>(Func<TSource, TResult> func) { _cases.Add(new CaseInfo<TResult> { Func = x => func((TSource)x), Target = typeof(TSource) }); return this; } }