It seems that you came across an interesting design decision, and not a mistake, it was intentionally - a dynamic function. I had in mind a blog about this for some time.
First of all, let's take a step back. The basic idea of a dynamic function is that an expression containing an operand of a dynamic type has an analysis of its type, deferred to runtime. At run time, type analysis is performed freshly by creating a new version of the compiler and reanalyzing it, this time examining a dynamic expression as if it were an expression of its actual type of runtime.
So, if you have an addition that at compile time has a compile time type on the left side and a dynamic compile time type on the right side, and at run time the dynamic expression is actually a string, then the analysis is redone on the left side, which is the object, and the right side is a string. Please note that the runtime type of the left side does not count . It compiles the type of time - an object, not a dynamic one. Only expressions of a dynamic type have the property that their execution types are used in run-time analysis.
Just make sure it is clear: if you have:
void M(Giraffe g, Apple a) {...} void M(Animal a, Fruit f) { ... } ... Animal x = new Giraffe(); dynamic y = new Apple(); M(x, y);
then at run time a second override is called. The fact that at run time x is a Giraffe is ignored because it is not dynamic. It was Animal at compile time, and therefore at runtime it continues to be parsed as an expression of type Animal. That is, the analysis is done as if you said:
M(x, (Apple)y);
and obviously chooses a second overload.
Hope this is clear.
Now we come to meat problems. What happens when the runtime type is not available? . Let it actually work out an example:
public class Fruit {} public class Apple : Fruit { public void M(Animal a) {} private class MagicApple : Apple { public void M(Giraffe g) {} } public static Apple MakeMagicApple() { return new MagicApple(); } } ... dynamic d1 = Apple.MakeMagicApple(); dynamic d2 = new Giraffe(); d1.M(d2);
Ok what's going on? We have two dynamic expressions, therefore, according to my previous statement, we run the analysis again at runtime, but pretend that you said
((Apple.MagicApple)d1).M((Giraffe)d2));
And so you would think that overload resolution would choose the Apple.MagicApple.M method that exactly matches that. But this is not so! We cannot pretend that the code above is what you said, because this code refers to a closed nested type outside its accessibility domain! . This code cannot be fully compiled. But it is equally obvious that we cannot let this code fail, because this is a common scenario.
Therefore, I must undo my previous expression. What the runtime analysis engine actually does is pretend that you have inserted roles that you could legitimately insert. In this case, he realizes that the user could insert:
((Apple)d1).M((Giraffe)d2));
And overload resolution would Apple.M .
In addition: pretending casts always refer to class types. There may be an interface type or a type type of type that can be inserted, which can lead to a successful overload, but using "dynamic", you indicated that you want to use the runtime type, and the object's runtime type is never an interface type parameter or type.
You seem to be in the same boat. If the execution type of the dynamic expression would not be available on the call site, then it is considered to be its closest available base type for runtime analysis purposes. In your case, the closest available base type may be an object.
Is this all clear?