.NET 4中奇怪的开关行为

10

我不理解以下代码导致编译错误的原因:

static class Program
{
    static void Main()
    {
        dynamic x = "";
        var test = foo(x);

        if (test == "test")
        {
            Console.WriteLine(test);
        }

        switch (test)
        {
            case "test":
                Console.WriteLine(test);
                break;
        }
    }

    private static string foo(object item)
    {
        return "bar";
    }
}

我遇到的错误在 switch (test) 这一行:

A switch expression or case label must be a bool, char, string, integral, 
enum, or corresponding nullable type.

智能提示显示foo操作将在运行时解决,这很好,因为我使用动态类型作为参数。然而,我不明白为什么if条件编译得很好,而switch却不行。

上面的代码只是我在应用程序(VSTO)中拥有的内容的简化版本,它出现在将应用程序从VSTO3迁移到VSTO4之后,当VSTO中的一个方法被更改以返回dynamic类型值而不是object时。

有人可以给我解释一下问题所在吗?我知道如何解决它,但我想了解发生了什么。


看起来你不能使用ducktype(鸭子类型)来实现switch语句。 - Matt Ellen
由于并非为每种可能的对象类型定义了 switch,因此无法使用。 - thecoop
每个人似乎都在回答问题“为什么我不能在动态上执行switch?” 我认为问题真正应该是“为什么test一开始被定义为动态类型?” 我理解如果 foo 有返回不同类型的重载,那么 test 必须是动态类型; 但是既然它没有,我仍然很难弄清楚这里“真正”的问题的答案。 - Dan Tao
我怀疑它在 C# 规范中——如果一个表达式有一个 dynamic 输入,则输出也是 dynamic,无论表达式是否只有一种可能的解释(比如,你可以使用反射添加另一个方法)。 - thecoop
5个回答

10
因为您正在动态调用方法,所以结果也是动态的(因为返回值可以是任何值-直到运行时它才知道)。而且您无法使用动态变量类型进行switch。

dynamic 上使用 if 是可以的吗? - RaYell
@RaYell:你不应该在dynamic上使用“if”- 你应该使用等于号==来进行比较。这是很明确的。 - Niki
为什么返回值可以是任何东西?foo返回一个字符串。我必须需要了解一下这个“动态”的疯狂…… - Dan Tao
1
@Dan,因为foo可能有多个重载版本,并且由于x是动态的,所以在x的类型未知之前无法确定将调用哪个版本,而这种情况直到运行时才会发生。 - Simon P Stevens
@Dan Tao:在我看来,Bill Wagner在《Effective C#》一书中给出了很好的解释。 - Matt Ellen
显示剩余2条评论

3

编译器在编译时评估开关表达式的类型。 dynamic 的类型在运行时评估,因此编译器无法验证它是否(或可转换为)允许的类型之一(根据 C# 4 Language Specification,这些类型是 sbyte、byte、short、ushort、int、uint、long、ulong、bool、char、string 或枚举类型)。


2
如Matt Ellen所说,但需要更多背景信息。
对于switch语句:来自C#语言规范v4.0: switch语句的管理类型由switch表达式确定。
  • 如果switch表达式的类型是sbytebyteshortushortintuintlongulongboolcharstring枚举类型,或它是与这些类型之一对应的可空类型,则是switch语句的管理类型。
  • 否则,必须存在恰好一个用户定义的隐式转换(§6.4),其类型从switch表达式的类型到以下可能的管理类型之一:sbytebyteshortushortintuintlongulongcharstring或与这些类型之一对应的可空类型。
  • 否则,如果不存在这样的隐式转换,或者存在多个这样的隐式转换,则会发生编译时错误。

对于if语句,表达式被评估为布尔运算。表达式的评估由变量赋值中方法调用中使用的dynamic引起延迟到运行时。从上面的规范来看,switch需要对switch类型进行编译时评估。

0

Switch语句仅支持数字、字符、枚举和字符串。 dynamic 不属于这些类型之一。如果您假设 x 是一个字符串,您可以将其强制转换:

dynamic x = ""; 
string test = (string)foo(x); 

如果不是,你只会得到一个运行时错误。


0
这是一些意外的行为 - 我本来期望将一个被显式返回字符串的方法赋值给变量会有效地推断出字符串类型。哎呀,算了吧......
如果你用以下代码替换:
var test = foo(x);
用下面的代码:
string test = foo(x);
你知道它会编译通过。
这是安全的,因为你已经声明了foo()方法返回一个字符串,并且在长期来看,这也更加直观易懂。

“var” 无法在运行时解析,因为 x 是动态的。可能会有多个“foo”的重载具有不同的参数和返回类型。直到了解 x 的类型(这需要在运行时才能完成),才可以解决发生的情况。实际发生的是 var 被解析为“dynamic”,这就是为什么它不能在 switch 中使用的原因。如果将鼠标指针悬停在“var”上,您将看到 Visual Studio 将其解析为“dynamic”。 - Simon P Stevens
是的 - 我看到了。我想我只是假设变量推断在方法调用所在的作用域中存在单个方法时会更加智能。但你知道关于假设的说法吧.... =) - Andrew Anderson

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