views:

184

answers:

5

I have a problem understanding what's causes the compilation error in the code below:

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";
    }
}

The error I get is in switch (test) line:

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

Intellisence shows me that foo operation will be resolved on runtime, which is fine because I'm using a dynamic type as a param. However I don't understand how if condition compiles fine when switch doesn't.

Code above is just simplified version of what I have in my application (VSTO) which appeared after migrating the app from VSTO3 to VSTO4 when one method in VSTO was changed to return dynamic type values instead of object.

Can anyone give me an explanation what's the problem. I know how to resolve it but I'd like to understand what's happening.

+4  A: 

Because you're calling a method dynamically, the result is also a dynamic (as the return value could be anything - it doesn't know until runtime). And you can't switch on a dynamic variable type.

thecoop
And it's ok to `if` on `dynamic`?
RaYell
@RaYell: You don't "`if` on `dynamic`" - you compare for equality `==` on `dynamic`. That's well defined.
nikie
OK, that makes sense.
RaYell
Why could the return value be anything? `foo` returns a `string`. I must need to read up on this `dynamic` craziness...
Dan Tao
@Dan, because there could be multiple overloads of foo, and because x is dynamic you can't decide exactly which will get called until x's type is known which won't happen until runtime.
Simon P Stevens
@Dan Tao: Bill Wagner gives a good explanation in Effective C# (IMO)
Matt Ellen
@Simon: There *could* be multiple overloads of `foo`, but in this case there is not, right? Doesn't it seem like the compiler should know what the return value will be if there's only one version of the function out there?
Dan Tao
@Matt: Thanks, I'll have to check it out.
Dan Tao
A: 

That is some unexpected behavior - I would have expected a var set to a method that explicitly returns a string to properly infer a string type. Oh well.....

If you replace:

var test = foo(x);

with:

string test = foo(x);

it all compiles as you know.

This is safe since you've declared foo() as returning a string, and perhaps a little more intuitive to read in the long run anyways.

Andrew Anderson
the 'var' can't be resolved at runtime because x is dynamic. There could potentially be multiple overloads of 'foo' that have different parameters and return types. Exactly what happens can't be resolved until the type of x is known, which won't happen until runtime. what actually happens is that var gets resolved to 'dynamic' which is why it can't be used in the switch. If you hover your mouse pointer over the 'var' you will see visual studio is resolving it to 'dynamic'
Simon P Stevens
Yes - I'm seeing that. I guess I just assumed that the var inference was smarter when faced with the presence of a single method in the scope where the method call was made. But you know what they say about assumptions.... =)
Andrew Anderson
+2  A: 

The type of the switch expression is evaluated by the compiler, at compile time. The type of dynamic is evaluated in runtime, so the compiler cannot verify whether it is (or is convertible to) one of the allowed types (which, according to the C# 4 Language Specification is sbyte, byte, short, ushort, int, uint, long, ulong, bool, char, string, or an enum-type).

Fredrik Mörk
+1  A: 

As Matt Ellen says, but with a little more background.

For the switch statement: from the C# Language Specification v4.0:

The governing type of a switch statement is established by the switch expression.

  • If the type of the switch expression is sbyte, byte, short, ushort, int, uint, long, ulong, bool, char, string, or an enum-type, or if it is the nullable type corresponding to one of these types, then that is the governing type of the switch statement.
  • Otherwise, exactly one user-defined implicit conversion (§6.4) must exist from the type of the switch expression to one of the following possible governing types: sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or, a nullable type corresponding to one of those types.
  • Otherwise, if no such implicit conversion exists, or if more than one such implicit conversion exists, a compile-time error occurs.

For the if statement the expression is evaluated as a Boolean operation. The expression evaluation is deferred to run-time because of the use of dynamic in the method call in the variable assignment. From the specification above, it looks like switch requires compile-time evaluation of switch types.

dariom
A: 

Switch statements only support numbers, chars, enums and strings. dynamic is none of those things. If you assume that x is a string, you can just cast it:

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

You'll just get a runtime error if it's not.

Richard Szalay