3

This comes from this question really: Looping through JSON array and adding items to list C# .

To not derail that question completely, I ask a new one that I started to wonder about.

Using Json.net, why does this code work:

dynamic test = new JValue("test");
string s = test;

while this:

dynamic test = new JValue("test");
var list = new List<string>();
list.Add(test);

throws:

RuntimeBinderException: The best overloaded method match for 'System.Collections.Generic.List<string>.Add(string)' has some invalid arguments

Given that there is just one overload for the Add method, and it takes a string, why won't it silently do the runtime conversion here as well?

Note that there is nothing specific about list.Add that makes this fail, this also fails:

dynamic test = new JValue("test");
Test(test);

...

public static void Test(string s) { }

First thing I could think of was that JValue has an implicit cast operator to string, but alas:

JValue test = new JValue("test");
string s = test;

does not compile, with:

CS0266 Cannot implicitly convert type 'Newtonsoft.Json.Linq.JValue' to 'string'. An explicit conversion exists (are you missing a cast?)

but it does say an explicit conversion exists, is this what "saves" the direct assignment to a string variable? But then, still, why didn't it save the usage as a parameter value where the only available overload takes a string parameter? Does assignment have extra rules perhaps?


After having thought some more about this I guess this question is actually the wrong question.

dynamic is not supposed to make more things work at runtime than they would at compile time, instead it's just meant to postpone the type binding to runtime.

So changing the type of the variable to a statically typed one, and neither of the examples work:

JValue test = new JValue("test");
string s = test; // CS0266 Cannot implicitly convert type 'Newtonsoft.Json.Linq.JValue' to 'string'. An explicit conversion exists (are you missing a cast?)

JValue test = new JValue("test");
Test(test); // same
Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • `JValue` implements `IConvertible` maybe this makes first assignment working? – Guru Stron Oct 22 '20 at 14:25
  • @GuruStron Could be, I fully understand that *something* must be different here and going on, I just wonder why `string variable = ` works and `list.Add()` doesn't work the same, but then I assume string assignment perhaps has some different rules. – Lasse V. Karlsen Oct 22 '20 at 14:26
  • Looking at the decompiled code there is call `<>o__0.<>p__0 = CallSite>.Create(Microsoft.CSharp.RuntimeBinder.Binder.Convert(CSharpBinderFlags.None, typeof(string), typeof(C)));` for assignment while for `Add` only `Binder.InvokeMember`. Which kind of makes sense - it would be much more work to analyze all possible overloads and conversions. – Guru Stron Oct 22 '20 at 14:28
  • Well, yes, but I thought the exact purpose of using `dynamic` is that you postponed this "analyze all possible overloads and conversions" to runtime. That's what it's supposed to do anyway. – Lasse V. Karlsen Oct 22 '20 at 14:29
  • 2
    The more I come to think about it, I think this is just the wrong question. `dynamic` is used to postpone handling code **that works** to runtime. This does not compile if I statically type it to `JValue`, so I guess that might be the answer. – Lasse V. Karlsen Oct 22 '20 at 14:31
  • Well if you statically type it to `JValue` then `string s = test` doesn't work either, while works with dynamic... – Evk Oct 22 '20 at 14:35
  • But I agree that these cases are quite different - in first you just need to check if there is conversion from `JValue` to `string`. However, in second case with method call, you might potentially have multiple overloads, and then you need to check if `JValue` is convertible to exactly one of them (so have to check all conversions), and it gets quite messy. – Evk Oct 22 '20 at 14:41
  • 1
    Note that `JValue` indirectly implements `IDynamicMetaObjectProvider`, which is likely why the first example actually works at runtime. This doesn't explain why the second example fails at runtime, though. – Matthew Watson Oct 22 '20 at 14:47
  • @Evk I agree, but there is exactly one overload that could be the candidate here because there is just one, at all. But I suspect completely different pieces of code handle the two, as GuruStron showed above, a normal invoke is done, and that might be all the difference. – Lasse V. Karlsen Oct 22 '20 at 15:35
  • @MatthewWatson is correct. `JValue` implements `IDynamicMetaObjectProvider`. The class returned, [`DynamicProxyMetaObject`](https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/DynamicProxyMetaObject.cs) has handcrafted logic logic for implicitly trying to cast `JValue` to the declared return type for certain types of expression. – dbc Oct 22 '20 at 15:42
  • I think it's in [`JValueDynamicProxy.TryConvert()`](https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Linq/JValue.cs#L971) I believe, called from [`BindConvert`](https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/DynamicProxyMetaObject.cs#L71). I don't really know much about the details of `IDynamicMetaObjectProvider` implementations though... – dbc Oct 22 '20 at 15:43
  • Closely related: [this answer](https://stackoverflow.com/a/29053805/3744182) to [Null-coalescing operator returning null for properties of dynamic objects](https://stackoverflow.com/q/29051663/3744182). – dbc Oct 22 '20 at 15:44

0 Answers0