-
Hey folks, My collegue and I were in around some oddly structured legacy code today and noticed something we were both surprised by that wasn't picked up by the compiler. When passing a dynamic parameter in to a method, even if the method is not awaitable, if you call await on it, the value is returned without any compiler warning. The example class below compiles without issue. public class Test
{
private IEnumerable<dynamic> NonAwaitableMethod(string input)
{
yield return input;
}
public async Task<IEnumerable<dynamic>> BorkedDynamicAsync(string input)
{
dynamic value = "String";
var awaitedIEnumerableStringList = await NonAwaitableMethod(value);
return awaitedIEnumerableStringList;
}
} Expected Result: 'IEnumerable' does not contain a definition for 'GetAwaiter' and no accessible extension method 'GetAwaiter' accepting a first argument of type 'IEnumerable' could be found (are you missing a using directive or an assembly reference?) Actual Result: No errors Debugging If you run this example with the following Program.cs main. the program completes successfully. static async Task Main(string[] args)
{
var testService = new Test();
Console.WriteLine("Hello Borked World! - " + testService.BorkedDynamicAsync("Chaos"));
Console.ReadKey();
} Output: Hello Borked World! - System.Threading.Tasks.Task When awaited we rightly get the exception I would expect to see. static async Task Main(string[] args)
{
var testService = new Test();
Console.WriteLine("Hello Borked World! - " + (await testService.BorkedDynamicAsync("Chaos")).First());
Console.ReadKey();
} Output: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: ''AwaitSyncTest.Test.d__0' does not contain a definition for 'GetAwaiter'' Feels odd, I'm nt sure there is a reason for this, but thought it best be reported. Thanks |
Beta Was this translation helpful? Give feedback.
Replies: 16 comments 22 replies
-
|
Beta Was this translation helpful? Give feedback.
-
Hey @HaloFour Interesting the entire expression is not evaluated until runtime as soon as dynamic becomes involved. Would it be best placed to check the return type of the method in an analyzer to make this feedback early in the development loop. Though this example is clearly kind of crazy, we had an issue of somthing similar in a library making it though our CI/CD process as this did not flag up what clearly would have been an error at runime (yes, no tests were involved in this process :( ). |
Beta Was this translation helpful? Give feedback.
-
This is really interesting. I can understand the semantics after analysing this (and also thanks to the helpful update from @HaloFour) but I can also see huge scope for unintentional problems. I wonder if it would be helpful for the compiler to issue a level 4 warning when this kind of dynamic evaluation spans more than a single expression. Maybe @micklaw's suggestion of an analyzer would be more "light touch" and less likely to annoy regular users of the dynamic keyword. |
Beta Was this translation helpful? Give feedback.
-
dynamic is used for late bound and it's a way to tell the compiler "I know that, at run-time, this will be there". Pretty much like the ! operator for nullable reference types. If you want compile-time checks, don't use dynamic. I don't claim to have seen all the code in the world but, I've seem very few legitimate uses of dynamic and lots of abuses of it. |
Beta Was this translation helpful? Give feedback.
-
Yeah, I absolutely get that perspective my issue is not with the technical details of why it is ignored, it is more that it is not obvious that using dynamic in this context with these side effects is not well known by the community IMO. For instance, the code we have inherited has been poorly constructed, and there are two methods of similar names doing similar things, one is async, one is not. Though simply because their input is dynamic type safety is lost causing the wrong method to easily be selected without compile warnings. I personally do not believe that it is common knowledge using the dynamic keyword causes the entire expression to be ignored until runtime, and feel it dangerous for us to make the assumption that all dotnet developers understand this too. Edit: Also, I agree using dynamic = bad. Though in this case it is having no operation done on it, it is simply used to pass its value to a method. |
Beta Was this translation helpful? Give feedback.
-
Check here: The dynamic type If you feel something is missing or not clear on the docs, open an issue right there. Feel free to cc me. |
Beta Was this translation helpful? Give feedback.
-
Thanks, and yes updating the docs could be a good shout here, though probably in honesty wouldn't have helped us in this case. Finally, I'd argue that... dynamic dynamic_ec = new ExampleClass();
dynamic_ec.exampleMethod1(10, 4); ...is a world away from. public class Test
{
public async Task<IEnumerable<string>> AsyncMethod(string input)
{
dynamic value = "String";
var awaitedIEnumerableStringList = await SomeMethod(value);
return awaitedIEnumerableStringList;
}
private IEnumerable<string> SomeMethod(string input)
{
yield return input;
}
} Happy to be proven that my knowledge of the dynamic keyword is less detailed than it should be though, and thats something I'll take on board. |
Beta Was this translation helpful? Give feedback.
-
Any expression involving dynamic is dynamic. That is the part that might be better documented. But I fail to understand where |
Beta Was this translation helpful? Give feedback.
-
I'm glad we agreed on the first point :) On the latter point though, dynamic is just an input to the method 'SomeMethod', I get that whatever the dynamic parameter is doing we do not know until runtime. What we do know however at compile time is that the return type of 'SomeMethod' is never going to be awaitable, so why not flag that? There is obviously some low level technical reason I am missing here and that's fine, it just seems strange to me that something so immediately obvious to the eye can not be caught by the compiler or an Analyzer, or maybe it can and just needs written? |
Beta Was this translation helpful? Give feedback.
-
@micklaw Unfortunately if any part of an expression is dynamic, even an input to a method call, overload resolution is pushed to runtime and therefore the C# compiler cannot 100% deduce the return type of the method you have called. Therefore the whole expression has the type I hope that makes sense. I know that when dynamic first came out I struggled with similar issues. |
Beta Was this translation helpful? Give feedback.
-
How does the compiler know that the runtime will resolve it to the particular method that you have in mind? |
Beta Was this translation helpful? Give feedback.
-
It won't. My point is that dumping a whole expression to the runtime just as cause a single input property is a dynamic type feels harsh, thats all. As I say, I'm happy to be wrong in my assumption that this would not be clear to 'most' dotnet devs. My colleague and I had no idea that would be the case. |
Beta Was this translation helpful? Give feedback.
-
And the point that @Richiban and @yaakov-h were making is that method overload resolution depends on the types of that method's arguments, and with |
Beta Was this translation helpful? Give feedback.
-
Clearly that is where the gap in my knowledge comes in to play, so there is a TIL element here for me in there is nothing that can be done here in any capacity when dynamic is used. Thank you for your time and effort. |
Beta Was this translation helpful? Give feedback.
-
@micklaw, that's why I said you should open issues on those pages if you fill information is missing. |
Beta Was this translation helpful? Give feedback.
-
I understand if an expression depends on a dynamic x = ...;
var y = x.Prop + x.Prop2; // y becomes dynamic But in the OP, it has |
Beta Was this translation helpful? Give feedback.
dynamic
is viral like this. Becausevalue
is declared asdynamic
everywhere you usevalue
as a parameter the entire expression becomes dynamic and won't be evaluated until runtime.