7

Given this short example program:

    static void Main(string[] args)
    {
        Console.WriteLine(Test("hello world"));
    }

    private static int Test(dynamic value)
    {
        var chars = Chars(value.ToString());
        return chars.Count();
    }

    private static IEnumerable<char> Chars(string str)
    {
        return str.Distinct();
    }

When run, it will produce an exception similar to:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: ''object' does not contain a definition for 'Count''

Meaning compiler chose dynamic as a preferred type of chars variable.

Is there any reason for it not to choose IEnumerable<char> as a concrete type, considering dynamic is not returned from Chars method? Just changing the type manually to IEnumerable<char> solves the issue, but I'm wondering why is dynamic a default value in this case?

Edit

I've probably used example which was more complex than necessary. It seems that the question asked here:

Anomaly when using 'var' and 'dynamic'

Provides more concise example and some insights as to why it works the way it does.

https://blogs.msdn.microsoft.com/ericlippert/2012/11/05/dynamic-contagion-part-one/

Describes how compiler handles dynamics.

Alex
  • 923
  • 9
  • 21
  • 1
    I thought it was to do with `value` being `dynamic`, changing that to `string` resolves the issue without modifying `Chars`. I don't know _why_ though and would also be interested to know. – Equalsk Oct 02 '17 at 14:46
  • 3
    With a `dynamic` argument, overloads are resolved at runtime. Therefore, it declines to guess at compile time what method is actually being called when you call `Chars()`. – 15ee8f99-57ff-4f92-890c-b56153 Oct 02 '17 at 14:47
  • What happens when you leave `.ToString()` out? – H H Oct 02 '17 at 14:49
  • 3
    The compiler does not know anything about ToString(). You might as well use ToFoo() and it will still compile. So everything gets dynamically bound, even the Chars() call. But Count() is an extension method, they won't be found by the DLR. Simplest workaround is to help the compiler get it right with `(string)value.ToString()`. – Hans Passant Oct 02 '17 at 14:51
  • @HenkHolterman It will fail with the same exception. – Alex Oct 02 '17 at 15:10
  • @HansPassant I think that's the missing piece here, I was not aware that Chars() is dynamically bound. – Alex Oct 02 '17 at 15:10
  • "... with the same exception" is what I expected. Shows that the arg to Chars() is dynamic. – H H Oct 02 '17 at 19:22

4 Answers4

5

With dynamic, all method calls are resolved at runtime. Therefore, it declines to guess at compile time what method is actually being called when you call Chars(), Count(), or even ToString(). It could be anything, returning anything. This is often called "dynamic contagion".

For all the compiler knows, somtimes value.ToString() will return MyRandomPOCOClass, and at runtime it'll be able to dig up some overload like Tuple<int,String> Chars(MyRandomPOCOClass x). Maybe next time value.ToString() will return int. All bets are off. dynamic turns C# into a scripting language.

Here's an example of dynamic runtime overload behavior (here's a fiddle):

public static void Main()
{
    dynamic x = "foo";

    Test(x);

    x = 34;

    Test(x);
}

public static void Test(string s)
{
    Console.WriteLine("String " + s);
}
public static void Test(int n)
{
    Console.WriteLine("Int " + n);
}

Output:

String foo
Int 34
  • 1
    Also, at run time, the likelihood is that value.Tostring() will indeed be resolved to string.ToString() in this case, which has a logical implementation of "return this", since https://msdn.microsoft.com/en-us/library/8tc6ws5s(v=vs.110).aspx says... "this method simply returns the current string unchanged", Net effect, dynamic in = dynamic out. – Dragonthoughts Oct 02 '17 at 14:56
  • Thanks, first paragraph explains the issue quite well, and it makes sense why it works like it does. – Alex Oct 02 '17 at 15:50
2

With dynamic value, how is the compiler to know what value.ToString() returns?

It has the same name as our familiar C# method, but it's different.

H H
  • 263,252
  • 30
  • 330
  • 514
  • Shouldn´t this throw compiler-error as `Chars(dynamic)` doesn´t exist? I tried it, it doesn´t produce that error, making me wonder why. – MakePeaceGreatAgain Oct 02 '17 at 14:52
  • 3
    @HimBromBeere If you want the compiler to tell you if a method does or doesn't exist, or if there is a problem with one of the types of your values, then don't turn the compiler off and tell it to not even try checking the types of any of the involved expressions by using `dynamic`. – Servy Oct 02 '17 at 14:54
  • @HimBromBeere It won't try to resolve the overload at compile time. At runtime, if the argument is `int`, it'll look for `Chars(int)`. – 15ee8f99-57ff-4f92-890c-b56153 Oct 02 '17 at 14:54
  • The whole point of `dynamic` is that it can serve as a stand-in for any value. `Chars(whatever)` is fine; at runtime a conversion will be attempted to `whatever`. The method itself must still exist (you can't call `Bars`). – Jeroen Mostert Oct 02 '17 at 14:54
  • @HimBromBeere Here's an example: https://dotnetfiddle.net/kuuGQM – 15ee8f99-57ff-4f92-890c-b56153 Oct 02 '17 at 14:57
0

Because the parameter passed to Test() is dynamic, and that parameter is passed to Chars(). Dynamic essentially defers type checking to the runtime for any statements that involve dynamic types, and that is "viral", in the sense that any statement involving a dynamic variable can only yield a dynamic variable.

Chris Shain
  • 50,833
  • 6
  • 93
  • 125
0

When you use str.Distinct(), it returns a object of type System.Linq.Enumerable DistinctIterator of char type to your chars variable under IEnumerable<char>. That does not have a Count method itself. It is an iterator internally returning value one by one. Cast it to IEnumerable<char> to get the count. You can loop over the IEnumerable and print the values (for loop below works fine because it is an iterator) but the underlying returned value from the set does not have Count method, unless variable of type iterator is cast to IEnumerable<char>. Otherwise, you can use str.Distinct().ToList() and that will work fine without casting.

static void Main(string[] args)
{
    Console.WriteLine(Test("hello world"));
}

private static int Test(dynamic value)
{
    var chars = Chars(value.ToString());
    //foreach (var c in chars)
    //        Console.WriteLine(c);
    return ((IEnumerable<char>)chars).Count();
}

private static IEnumerable<char> Chars(string str)
{
    return str.Distinct();
}
Amit Kumar Singh
  • 4,393
  • 2
  • 9
  • 22