4

In my code (.NET6.0) I have a class with two very similar methods. One is not generic, but has a second optional parameter. Second method is generic with only one (generic) parameter.

var c = new C();
var stringParam = "normal string";
dynamic dynamicParam = stringParam;
c.M(stringParam);
c.M(dynamicParam);

public interface II { }

public class C
{
    public void M(string param, TimeSpan? dt = null) => Console.WriteLine("Not generic");
    public void M<T>(T param) where T : II => Console.WriteLine("Generic");
}

When I run this code, it fails at the calling of method M with dynamicParam. I get a runtime exception

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'The type 'string' cannot be used as type parameter 'T' in the generic type or method 'C.M(T)'. There is no implicit reference conversion from 'string' to 'II'.'

What I don't understand is why the runtime does not in both cases use the non generic method, because I don't call it in a generic way.

When I remove second optional nullable parameter from the first method, it works - non generic method is called in both cases.

When I remove second (generic) method, it also works.

When I compile the code and check it with ILSpy, both methods look really different. So why does it not work with both methods as they are?

I have checked other SO questions with the same error message, and I believe that my issue has a different cause.

Charlieface
  • 52,284
  • 6
  • 19
  • 43
Beyboy
  • 63
  • 2
  • 2
    Well, the dynamic type is not a string that's why the second signature gets picked. And since it's dynamic it will be decided in runtime what type it has. So the generic signature is picked here and then since the type `string` doesn't implement the `ÌI` interface you get a runtime error. – Eldar Jul 23 '22 at 20:48
  • I would argue that in general using `dynamic` should be avoided, usually it is a code smell. – Guru Stron Jul 23 '22 at 20:53
  • @Eldar That's not relevant. What is relevant is that the function which has no optional parameters is higher precedence in method group conversion (ie matching the arguments to a function), therefore the first one is ignored. See also https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#11643-better-function-member – Charlieface Jul 24 '22 at 02:14
  • @Charlieface The issue is, why compile-time binding behaves differently than run-time binding. – TN. Jul 24 '22 at 06:33
  • @Charlieface what you mention happens in the first call. That's why it succeeds and I was talking about the second call which fails. – Eldar Jul 24 '22 at 09:00
  • @Guru Stron yes I agree. I eventually I "solved" my problem by casting the dynamic property to string, and them the problem disappeared. However, it took me a long time to find out this "solution" because actually I didn't know that the parameter type was dynamic. – Beyboy Jul 24 '22 at 09:38
  • @Charlieface thanks for the references! This whole overload resolution thing seems a bit too complicated for me to memorize. Next time I will just be sure not to deal with dynamic parameters. – Beyboy Jul 24 '22 at 09:43
  • @TN Hmm you might be right. Interestingly enough, I get a compile-time error only in .Net 4.7.2 on the *first* line, whereas .Net 6 as well as Roslyn it throws the runtime error on the second. https://dotnetfiddle.net/Q1S3fj Weirdly I cannot replicate that in LinqPad 5, which runs .Net 4.8. I suspect that at some point the overload resolution algorithm changed slightly, but the one used at runtime for `dynamic` was not updated. A careful reading through the spec seems to indicate the runtime error is a bug, but can't be sure – Charlieface Jul 24 '22 at 11:01
  • Perhaps Eric Lippert will chime in, or @JonSkeet – Charlieface Jul 24 '22 at 11:03

0 Answers0