在C#中根据类型使用switch case语句

107
可能重复:

参考:
C# - 有没有比这个更好的“类型切换”替代方案?

你好,假设我在类类型上遇到一个大的 if/else。有没有使用 switch case 的方法来解决它呢?

例如:

function test(object obj)
{
if(obj is WebControl)
{

}else if(obj is TextBox)
{

}
else if(obj is ComboBox)
{

}

“等等……”
“我想要创建这样的东西。”
switch(obj)
{
case is TextBox:
break;
case is ComboBox:
break;

}

}


1
参见以下内容: https://dev59.com/HnVC5IYBdhLWcg3w4VVz https://dev59.com/ZGsz5IYBdhLWcg3w9syr https://dev59.com/5G855IYBdhLWcg3wMRVW https://dev59.com/qHVC5IYBdhLWcg3wbgdS - Mikhail Poda
1
and https://dev59.com/6XVD5IYBdhLWcg3wGHau https://dev59.com/61rUa4cB1Zd3GeqPkoIM http://stackoverflow.com/questions/6304815/why-is-this-switch-on-type-case-considered-confusing https://dev59.com/hVfUa4cB1Zd3GeqPDwfX https://dev59.com/s2LVa4cB1Zd3GeqPx5cP https://dev59.com/c0zSa4cB1Zd3GeqPqefO - Mikhail Poda
7
针对任何来到这个页面的人的更新:从 C# 7 开始,你完全可以使用类型写 switch case 语句。参见:https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/ - LanderV
请在此处输入要翻译的文本。 - Thierry Prost
5个回答

172

更新C# 7

是的:来源

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# 7 之前

不可以。

http://blogs.msdn.com/b/peterhal/archive/2005/07/05/435760.aspx

我们收到了很多关于 C# 语言的添加请求,今天我要谈论其中一个比较常见的请求 - 根据类型进行 switch。根据类型进行 switch 看起来是一个非常有用和直接的功能:添加一个类似 switch 的结构,根据表达式的类型而不是值进行切换。可能会像这样:

switch typeof(e) { 
        case int:    ... break; 
        case string: ... break; 
        case double: ... break; 
        default:     ... break; 
}

这种语句对于在不同类型层次结构中添加虚拟方法(如分配方法),或者在包含您不拥有的类型的类型层次结构上非常有用。通过看到此类示例,您可以轻松得出该功能将是简单而有用的结论。它甚至可能让您开始思考“为什么那些#*&%$懒惰的C#语言设计师不会使我的生活更加轻松并添加这个简单的节省时间的语言特性?”

不幸的是,像许多“简单”的语言特性一样,类型切换并不像它首先表现得那么简单。当你看一个更重要、同样重要的例子时,问题就开始了:

class C {}
interface I {}
class D : C, I {}

switch typeof(e) {
case C: … break;
case I: … break;
default: … break;
}

链接: https://blogs.msdn.microsoft.com/peterhal/2005/07/05/many-questions-switch-on-type/


5
我不同意语言设计者。许多语言都有类型转换。虽然没有类型转换,但很容易实现,详见https://dev59.com/RWw05IYBdhLWcg3wahNN#7301514。 - cdiggins
41
答案的剩余部分在哪里?现在它的结尾是"...,例如像这样:" - oɔɯǝɹ
2
我不在乎他说什么。这很“简单”。如果当初设计C#更好的话,事情会更简单。听说所有语法规则需要进行10+次通行 :(. Switch 本质上是 obj.member。vtable 是一个无法访问的成员。如果将其视为值(如 int),则可以使用它。 - user34537
5
如果你想阅读整篇文章,我已经提供了信息来源的链接。我不会复制整个引用,因为你可以在原始来源处阅读它。简单的答案是“不”。 - Steve
6
针对任何来到这个页面的人更新一下:自C# 7开始,你完全可以编写基于类型的switch cases。具体请参见:https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/。 - LanderV
显示剩余7条评论

62
以下代码基本上按照预期的那样工作,它只查看实际类型(例如,由GetType()返回的内容)。
public static void TestTypeSwitch()
{
    var ts = new TypeSwitch()
        .Case((int x) => Console.WriteLine("int"))
        .Case((bool x) => Console.WriteLine("bool"))
        .Case((string x) => Console.WriteLine("string"));

    ts.Switch(42);     
    ts.Switch(false);  
    ts.Switch("hello"); 
}

这里是使其工作所需的机械设备。
public class TypeSwitch
{
    Dictionary<Type, Action<object>> matches = new Dictionary<Type, Action<object>>();
    public TypeSwitch Case<T>(Action<T> action) { matches.Add(typeof(T), (x) => action((T)x)); return this; } 
    public void Switch(object x) { matches[x.GetType()](x); }
}

2
非常有趣的解决方案...在某些方面这是可怕的 :) ...但在某些方面这是令人难以置信的(特别是如果其他外部开发人员可以通过创建类“X”,然后提供“如何处理X”的逻辑来利用该系统...有点像迷你DI/Ioc) - Timothy Khouri
9
“在某些方面这是可怕的”是需要翻译的内容。 - Pedro77
1
@cdiggins,我能加入类似“默认”或“无”之类的东西吗? - Pedro77
1
哇!非常有趣的解决方案! - edotassi
1
@Pedro77 是的,你可以在 Switch 方法中检查 Key 的存在性,如果不存在,则在 Case 链的末尾使用 typeof(Object) 作为默认情况。 - Mrinal Kamboj
显示剩余2条评论

28

是的,你可以通过名称进行切换...

switch (obj.GetType().Name)
{
    case "TextBox":...
}

1
请注意,当给出一个 public class MyCustomTextBox : TextBox 时,此解决方案将无法工作。 - Steve
10
假设您知道所有可能的子类,那当然可以。 - Steve
3
我想象这位编写代码的人是有原因的...并且知道所有的子类 :P - 例如,他可能正在尝试为所有“文本框”添加CssClass名称,然后为所有“日期选择器”或其他东西添加。 - Timothy Khouri
3
我知道这已经有些过时了,但我喜欢你的答案,并且同意你的推论,即只要使用 switch 语句来指定特定类别的操作,而不是所有子类,写这篇文章的人应该知道他们的所有子类。 - cost
1
这段代码是一个解决方法,因为它在编译时没有被检查,所以很容易创建一个无法到达的代码。 - Jiří Herník
显示剩余4条评论

15
这里有一个选项,我尽可能地符合OP的要求,能够根据类型进行切换。如果你足够仔细地看,它几乎看起来像一个真正的switch语句。
调用代码如下:
var @switch = this.Switch(new []
{
    this.Case<WebControl>(x => { /* WebControl code here */ }),
    this.Case<TextBox>(x => { /* TextBox code here */ }),
    this.Case<ComboBox>(x => { /* ComboBox code here */ }),
});

@switch(obj);

每个lambda表达式中的x都是强类型的,不需要转换类型。
为了使这个魔法生效,你需要这两个方法:
private Action<object> Switch(params Func<object, Action>[] tests)
{
    return o =>
    {
        var @case = tests
            .Select(f => f(o))
            .FirstOrDefault(a => a != null);

        if (@case != null)
        {
            @case();
        }
    };
}

private Func<object, Action> Case<T>(Action<T> action)
{
    return o => o is T ? (Action)(() => action((T)o)) : (Action)null;
}

几乎让你热泪盈眶,对吧?

尽管如此,它确实有效。享受吧。


1
+1 确实很聪明。不确定我会在生产代码中使用它(并不是说它有什么 问题)。 - Steve
3
“new [] { }”是多余的。 - cdiggins
哈哈,不错。我同意@Steve但+1。 - Doctor Jones
@cdiggins - 我同意它们是多余的,但我编写了代码以允许放弃数组初始化程序,而选择参数列表。在我看来,一旦你有超过三个参数,数组初始化程序语法会更易读一些。如果你足够努力地眯起眼睛,它看起来更像正常的switch语法! - Enigmativity
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Mrinal Kamboj

11

最简单的方法可能是使用动态语言,即像Yuval Peled答案中所定义的那样定义简单方法:

void Test(WebControl c)
{
...
}

void Test(ComboBox c)
{
...
}

你不能直接调用Test(obj),因为重载决策是在编译时完成的。你必须将对象分配给一个动态变量,然后调用Test方法:

dynamic dynObj = obj;
Test(dynObj);

1
很棒的解决方案!我投了赞成票 :-) - Volker von Einem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接