Why dynamic .ToString() (string.Format(), Convert.ToString()) returns dynamic and not string?

This question is for educational purposes only. I have this code that fails at compile time on line where I want to write lines to file. (File.WriteAllLines(@"C:\temp\processed.txt",contents);)

Error messages:

Argument 2: cannot convert from 'System.Collections.Generic.List<dynamic>' to 'string[]' C:\hg\PricingEngine\Source\App\Support\PricingEngine.Migrator\Program.cs 49 57 PricingEngine.Migrator

Error 6 The best overloaded method match for 'System.IO.File.WriteAllLines(string, string[])' has some invalid arguments C:\hg\PricingEngine\Source\App\Support\PricingEngine.Migrator\Program.cs 49 13 PricingEngine.Migrator

If I comment out last line and inspect with breakpoint all lines are strings, and everything works just fine.

Code:

public static void Main()
{
    var t = new List<dynamic>
    {
        new {name = "Mexico", count = 19659},
        new {name = "Canada", count = 13855},
    };

   var stringed =  t.Select(o => string.Format("{0} {1}", o.name, o.count)).Select(o => Convert.ToString(o)).ToList();
   File.WriteAllLines(@"C:\temp\processed.txt", stringed);
 }

What is going on here why dynamic ToString(), string.Format() and Convert.ToString() is dynamic? Am I missing something obvious here?

Jon Skeet
people
quotationmark

It seems to me like your question can really be boiled down to:

dynamic d = "x";
var v = Convert.ToString(d);

... the compile-time type of v is dynamic, as shown by hovering over it in Visual Studio, and you'd expect it to be string. No need for lists or files.

So, why is this? Basically, C# has a simple rule: almost any operation that uses a dynamic value has a result of dynamic. That means it doesn't matter if extra overloads are available at execution time that aren't known at compile time, for example.

The only operations I'm aware of where the result of an operation involving a dynamic value isn't dynamic are:

  • The is operator, e.g. var b = d is Foo; // Type of b is bool
  • A cast, e.g. var x = (string) d; // Type of x is string
  • The as operator, e.g. var y = d as string; // Type of y is string
  • Constructor calls, e.g. var z = new Bar(d); // Type of z is Bar

For the simple case of a method call, section 7.6.5 of the C# 5 specification makes it clear that Convert.ToString(d) will have a type of dynamic:

An invocation-expression is dynamically bound (ยง7.2.2) if at least one of the following holds:

  • The primary-expression has compile-time type dynamic.
  • At least one argument of the optional argument-list has compile-time type dynamic and the primary-expression does not have a delegate type.

In this case the compiler classifies the invocation-expression as a value of type dynamic.

(As a side-note, the "and the primary-expression does not have the delegate type" part doesn't seem to be resolved anywhere, or honoured by the compiler. If you have a Func<string, string> func = ...; var result = func(d); the type of result still appears to be dynamic rather than string. I shall look into that...)

people

See more on this question at Stackoverflow