看作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()); } }
JaredPar.. 270
在C#中肯定缺少开启类型(更新:在C#7/VS 2017中支持切换类型 - 请参阅下面的Zachary Yates的回答).为了在没有大的if/else if/else语句的情况下执行此操作,您需要使用不同的结构.我写了一篇博客文章,详细介绍了如何构建TypeSwitch结构.
http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx
简短版本:TypeSwitch旨在防止冗余转换,并提供类似于普通switch/case语句的语法.例如,这里是TypeSwitch对标准Windows窗体事件的操作
TypeSwitch.Do( sender, TypeSwitch.Case
TypeSwitch的代码实际上非常小,可以很容易地放入您的项目中.
static class TypeSwitch { public class CaseInfo { public bool IsDefault { get; set; } public Type Target { get; set; } public Action
"type == entry.Target"也可以更改为"entry.Target.IsAssignableFrom(type)"以考虑兼容类型(例如,子类). (25认同)
有一点值得注意的是(根据我的理解),需要最后指定"默认"操作以确保检查所有其他情况.我认为这不是标准开关中的要求 - 不是我曾经见过任何人试图在底部以外的任何地方设置'默认'.对此有两个故障安全选项可能是命令数组确保默认值是最后一点(有点浪费)或者弹出一个变量的默认值以便在`foreach`之后处理(只有匹配不是这样才会发生)发现) (3认同)
Zachary Yate.. 263
使用 Visual Studio 2017(版本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(""); 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语句,但是您必须使用包含类型名称的魔术字符串...这不是特别重构友好的(感谢@nukefusion)
switch(o.GetType().Name) { case "AType": break; }
我不喜欢这个答案,因为nameof(NamespaceA.ClassC)== nameof(NamespaceB.ClassC)是真的. (21认同)
(c#7)如果你不需要访问该对象,你也可以使用下划线:`case UnauthorizedException _:` (7认同)
@nukefusion:即使你使用闪亮的新[`nameof()`运算符](http://stackoverflow.com/questions/26573102/is-nameof-evaluated-at-compile-time). (6认同)
混淆可以打破它 (2认同)
Jon Skeet.. 101
一种选择是有从字典Type
到Action
(或其他一些代表).根据类型查找操作,然后执行它.我以前把它用于工厂.
在C#中肯定缺少开启类型(更新:在C#7/VS 2017中支持切换类型 - 请参阅下面的Zachary Yates的回答).为了在没有大的if/else if/else语句的情况下执行此操作,您需要使用不同的结构.我写了一篇博客文章,详细介绍了如何构建TypeSwitch结构.
http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx
简短版本:TypeSwitch旨在防止冗余转换,并提供类似于普通switch/case语句的语法.例如,这里是TypeSwitch对标准Windows窗体事件的操作
TypeSwitch.Do( sender, TypeSwitch.Case
TypeSwitch的代码实际上非常小,可以很容易地放入您的项目中.
static class TypeSwitch { public class CaseInfo { public bool IsDefault { get; set; } public Type Target { get; set; } public Action
使用 Visual Studio 2017(版本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(""); 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语句,但是您必须使用包含类型名称的魔术字符串...这不是特别重构友好的(感谢@nukefusion)
switch(o.GetType().Name) { case "AType": break; }
一种选择是有从字典Type
到Action
(或其他一些代表).根据类型查找操作,然后执行它.我以前把它用于工厂.
有了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; }
请注意,Case()
方法的顺序很重要.
获取我TypeSwitch
班级的完整注释代码.这是一个工作缩写版本:
public static class TypeSwitch { public static SwitchOn (TSource value) { return new Switch (value); } public sealed class Switch { private readonly TSource value; private bool handled = false; internal Switch(TSource value) { this.value = value; } public Switch Case (Action action) where TTarget : TSource { if (!this.handled && this.value is TTarget) { action((TTarget) this.value); this.handled = true; } return this; } public void Default(Action action) { if (!this.handled) action(this.value); } } }
创建一个超类(S)并使A和B继承它.然后在S上声明每个子类需要实现的抽象方法.
这样做"foo"方法也可以将其签名更改为Foo(S o),使其类型安全,并且您不需要抛出那个丑陋的异常.
如果您使用的是C#4,则可以使用新的动态功能来实现一个有趣的替代方案.我并不是说这更好,事实上它似乎很可能会变慢,但它确实有一定的优雅.
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#4动态方法调用在运行时而不是编译时解析了它的重载.我最近写了一些关于这个想法的文章.再说一次,我想重申一下这可能比其他所有建议都要糟糕,我只是把它作为一种好奇心来提供.
你应该真的超载你的方法,而不是试图自己消除歧义.到目前为止,大多数答案都没有考虑到未来的子类,这可能会导致以后真正糟糕的维护问题.
对于内置类型,您可以使用TypeCode枚举.请注意,GetType()有点慢,但在大多数情况下可能不相关.
switch (Type.GetTypeCode(someObject.GetType())) { case TypeCode.Boolean: break; case TypeCode.Byte: break; case TypeCode.Char: break; }
对于自定义类型,您可以创建自己的枚举,以及具有抽象属性或方法的接口或基类...
属性的抽象类实现
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; } }
我的一位同事刚刚告诉我这件事:这样做的好处是你可以将它用于任何类型的对象,而不仅仅是你定义的对象.它的缺点是更大更慢.
首先定义一个这样的静态类:
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 DictionarytypeDict; static TypeEnumerator() { typeDict = new Dictionary (); typeDict[typeof(int)] = TypeEnumeratorTypes._int; typeDict[typeof(string)] = TypeEnumeratorTypes._string; typeDict[typeof(Foo)] = TypeEnumeratorTypes._Foo; typeDict[typeof(System.Net.Sockets.TcpClient)] = TypeEnumeratorTypes._TcpClient; } /// /// Throws NullReferenceException and TypeEnumeratorException ///NullReferenceException ///TypeEnumeratorException 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; }
我喜欢Virtlink 使用隐式类型来使交换机更具可读性,但我不喜欢早期不可能,而且我们正在进行分配.让我们稍微调整一下.
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. }
好吧,这让我的手指受伤.我们在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("Actionaction{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#> }
稍微调整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());
可读且快速.现在,由于每个人都在指出他们的答案,并且考虑到这个问题的性质,顺序在类型匹配中很重要.因此:
首先放置叶子类型,稍后放置基本类型.
对于对等类型,首先放置更多可能的匹配以最大化perf.
这意味着不需要特殊的默认情况.相反,只需使用lambda中最基本的类型,并将其放在最后.
是的,多亏了可以实现的C#7。这是完成的过程(使用表达式模式):
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()); }
您可以在C#7或更高版本中使用模式匹配:
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; }
鉴于继承有助于将对象识别为多种类型,我认为切换可能导致不明确的歧义.例如:
情况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是一个字符串和一个对象.我想当你写一篇文章时,switch(foo)
你期望foo匹配一个且只有一个case
语句.通过打开类型,编写case语句的顺序可能会改变整个switch语句的结果.我认为这是错误的.
您可以考虑对"typeswitch"语句的类型进行编译器检查,检查枚举类型是否不相互继承.但这并不存在.
foo is T
是不一样的foo.GetType() == typeof(T)
!!