"开启类型"还有比这更好的选择吗?

xyz*_*xyz 312 c# switch-statement system.type

看作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());
    }
}
Run Code Online (Sandbox Code Playgroud)

Jar*_*Par 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<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"));
Run Code Online (Sandbox Code Playgroud)

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
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

  • "type == entry.Target"也可以更改为"entry.Target.IsAssignableFrom(type)"以考虑兼容类型(例如,子类). (25认同)
  • 有一点值得注意的是(根据我的理解),需要最后指定"默认"操作以确保检查所有其他情况.我认为这不是标准开关中的要求 - 不是我曾经见过任何人试图在底部以外的任何地方设置'默认'.对此有两个故障安全选项可能是命令数组确保默认值是最后一点(有点浪费)或者弹出一个变量的默认值以便在`foreach`之后处理(只有匹配不是这样才会发生)发现) (3认同)

Zac*_*tes 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("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}
Run Code Online (Sandbox Code Playgroud)

使用C#6,您可以使用带有nameof()运算符的switch语句(感谢@Joey Adams):

switch(o.GetType().Name) {
    case nameof(AType):
        break;
    case nameof(BType):
        break;
}
Run Code Online (Sandbox Code Playgroud)

使用C#5及更早版本,您可以使用switch语句,但是您必须使用包含类型名称的魔术字符串...这不是特别重构友好的(感谢@nukefusion)

switch(o.GetType().Name) {
  case "AType":
    break;
}
Run Code Online (Sandbox Code Playgroud)

  • 我不喜欢这个答案,因为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*_*eet 101

一种选择是有从字典TypeAction(或其他一些代表).根据类型查找操作,然后执行它.我以前把它用于工厂.

  • 次要注意:适用于1:1匹配,但可能是继承和/或接口的痛苦 - 特别是因为不能保证使用字典保留顺序.但是,这仍然是我在一些地方做的方式;-p所以+1 (31认同)
  • 如果你正在为这个目的专门构建一个字典,你可以重载索引器以返回键类型的值,或者如果缺少那么它的超类,如果缺少那么超级类等,直到没有任何东西. (5认同)
  • 这种技术因继承和接口而中断,因为您需要在正在检查的对象与您正在调用的委托之间进行一对一的对应.您应该尝试在字典中找到哪个对象的多个接口? (4认同)
  • 我过去经常使用这种技术,通常在转移到IoC容器之前 (2认同)

Dan*_*ker 49

有了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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 您还可以为初始案例添加扩展方法:`public static Switch <TSource> Case <TSource,TTarget>(此TSource值,Action <TTarget> action)其中TTarget:TSource`.这让你说'value.Case((C x)......` (2认同)

Pab*_*dez 14

创建一个超类(S)并使A和B继承它.然后在S上声明每个子类需要实现的抽象方法.

这样做"foo"方法也可以将其签名更改为Foo(S o),使其类型安全,并且您不需要抛出那个丑陋的异常.


Pau*_*tum 8

如果您使用的是C#4,则可以使用新的动态功能来实现一个有趣的替代方案.我并不是说这更好,事实上它似乎很可能会变慢,但它确实有一定的优雅.

class Thing
{

  void Foo(A a)
  {
     a.Hop();
  }

  void Foo(B b)
  {
     b.Skip();
  }

}
Run Code Online (Sandbox Code Playgroud)

用法:

object aOrB = Get_AOrB();
Thing t = GetThing();
((dynamic)t).Foo(aorB);
Run Code Online (Sandbox Code Playgroud)

这样做的原因是C#4动态方法调用在运行时而不是编译时解析了它的重载.我最近写了一些关于这个想法的文章.再说一次,我想重申一下这可能比其他所有建议都要糟糕,我只是把它作为一种好奇心来提供.


sep*_*332 7

你应该真的超载你的方法,而不是试图自己消除歧义.到目前为止,大多数答案都没有考虑到未来的子类,这可能会导致以后真正糟糕的维护问题.

  • 过载分辨率是静态确定的,因此根本不起作用. (2认同)

Edw*_*vey 7

对于内置类型,您可以使用TypeCode枚举.请注意,GetType()有点慢,但在大多数情况下可能不相关.

switch (Type.GetTypeCode(someObject.GetType()))
{
    case TypeCode.Boolean:
        break;
    case TypeCode.Byte:
        break;
    case TypeCode.Char:
        break;
}
Run Code Online (Sandbox Code Playgroud)

对于自定义类型,您可以创建自己的枚举,以及具有抽象属性或方法的接口或基类...

属性的抽象类实现

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; } }
}
Run Code Online (Sandbox Code Playgroud)

抽象类的实现方法

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; }
}
Run Code Online (Sandbox Code Playgroud)

属性的接口实现

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes FooType { get; }
}
public class FooFighter : IFooType
{
    public FooTypes FooType { get { return FooTypes.FooFighter; } }
}
Run Code Online (Sandbox Code Playgroud)

接口实现方法

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes GetFooType();
}
public class FooFighter : IFooType
{
    public FooTypes GetFooType() { return FooTypes.FooFighter; }
}
Run Code Online (Sandbox Code Playgroud)

我的一位同事刚刚告诉我这件事:这样做的好处是你可以将它用于任何类型的对象,而不仅仅是你定义的对象.它的缺点是更大更慢.

首先定义一个这样的静态类:

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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样使用它:

switch (TypeEnumerator.EnumerateType(someObject))
{
    case TypeEnumerator.TypeEnumeratorTypes._int:
        break;
    case TypeEnumerator.TypeEnumeratorTypes._string:
        break;
}
Run Code Online (Sandbox Code Playgroud)


sco*_*obi 6

我喜欢Virtlink 使用隐式类型来使交换机更具可读性,但我不喜欢早期不可能,而且我们正在进行分配.让我们稍微调整一下.

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.
}
Run Code Online (Sandbox Code Playgroud)

好吧,这让我的手指受伤.我们在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#>
}
Run Code Online (Sandbox Code Playgroud)

稍微调整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());
Run Code Online (Sandbox Code Playgroud)

可读且快速.现在,由于每个人都在指出他们的答案,并且考虑到这个问题的性质,顺序在类型匹配中很重要.因此:

  • 首先放置叶子类型,稍后放置基本类型.
  • 对于对等类型,首先放置更多可能的匹配以最大化perf.
  • 这意味着不需要特殊的默认情况.相反,只需使用lambda中最基本的类型,并将其放在最后.


Ser*_*ern 6

是的,多亏了可以实现的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());
}
Run Code Online (Sandbox Code Playgroud)


alh*_*hpe 6

您可以在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;
}
Run Code Online (Sandbox Code Playgroud)


Pil*_*iis 6

C# 8 对模式匹配的增强使得这样做成为可能。在某些情况下,它可以完成工作并且更简洁。

        public Animal Animal { get; set; }
        ...
        var animalName = Animal switch
        {
            Cat cat => "Tom",
            Mouse mouse => "Jerry",
            _ => "unknown"
        };
Run Code Online (Sandbox Code Playgroud)


Evr*_*glu 5

鉴于继承有助于将对象识别为多种类型,我认为切换可能导致不明确的歧义.例如:

情况1

{
  string s = "a";
  if (s is string) Print("Foo");
  else if (s is object) Print("Bar");
}
Run Code Online (Sandbox Code Playgroud)

案例2

{
  string s = "a";
  if (s is object) Print("Foo");
  else if (s is string) Print("Bar");
}
Run Code Online (Sandbox Code Playgroud)

因为s是一个字符串一个对象.我想当你写一篇文章时,switch(foo)你期望foo匹配一个且只有一个case语句.通过打开类型,编写case语句的顺序可能会改变整个switch语句的结果.我认为这是错误的.

您可以考虑对"typeswitch"语句的类型进行编译器检查,检查枚举类型是否不相互继承.但这并不存在.

foo is T是不一样的foo.GetType() == typeof(T)!!


Dav*_*zzo 5

根据 C# 7.0 规范,您可以声明作用域为 a caseof a的局部变量switch

object a = "Hello world";
switch (a)
{
    case string myString:
        // The variable 'a' is a string!
        break;
    case int myInt:
        // The variable 'a' is an int!
        break;
    case Foo myFoo:
        // The variable 'a' is of type Foo!
        break;
}
Run Code Online (Sandbox Code Playgroud)

这是执行此类操作的最佳方法,因为它只涉及强制转换和压入堆栈操作,这些操作是解释器可以运行的最快操作之一,前面是按位操作和boolean条件。

与 using 相比Dictionary<K, V>,这里的内存使用量要少得多,并且计算量基本为零。

另一方面,这应该与使用语句链一样快(如果不是更快的话)if

object a = "Hello world";
if (a is string)
{
    // The variable 'a' is a string!
} else if (a is int)
{
    // The variable 'a' is an int!
} // etc.
Run Code Online (Sandbox Code Playgroud)


小智 5

应该与

case type _:
Run Code Online (Sandbox Code Playgroud)

喜欢:

int i = 1;
bool b = true;
double d = 1.1;
object o = i; // whatever you want

switch (o)
{
    case int _:
        Answer.Content = "You got the int";
        break;
    case double _:
        Answer.Content = "You got the double";
        break;
    case bool _:
        Answer.Content = "You got the bool";
        break;
}
Run Code Online (Sandbox Code Playgroud)