C# Method overload resolution not selecting concrete generic override
Solution 1:
Looks like this is mentioned in the C# specification 5.0, 7.5.3 Overload Resolution:
Overload resolution selects the function member to invoke in the following distinct contexts within C#:
- Invocation of a method named in an invocation-expression (§7.6.5.1).
- Invocation of an instance constructor named in an object-creation-expression (§7.6.10.1).
- Invocation of an indexer accessor through an element-access (§7.6.6).
- Invocation of a predefined or user-defined operator referenced in an expression (§7.3.3 and §7.3.4).
Each of these contexts defines the set of candidate function members and the list of arguments in its own unique way, as described in detail in the sections listed above. For example, the set of candidates for a method invocation does not include methods marked override (§7.4), and methods in a base class are not candidates if any method in a derived class is applicable (§7.6.5.1).
When we look at 7.4:
A member lookup of a name N with K type parameters in a type T is processed as follows:
• First, a set of accessible members named N is determined:
If T is a type parameter, then the set is the union of the sets of
accessible members named N in each of the types specified as a primary constraint or secondary constraint (§10.1.5) for T, along with the set of accessible members named N in object.Otherwise, the set consists of all accessible (§3.5) members named N in T, including inherited members and the accessible membersnamed N in object. If T is a constructed type, the set of members is obtained by substituting type arguments as described in §10.3.2. Members that include an override modifier are excluded from the set.
If you remove override
the compiler picks the Execute(string)
overload when you cast the item.
Solution 2:
As mentioned in Jon Skeet's article on overloading, when invoking a method in a class that also overrides a method with the same name from a base class, the compiler will always take the in-class method instead of the override, regardless of the "specificness" of type, provided that the signature is "compatible".
Jon goes on to point out that this is an excellent argument for avoiding overloading across inheritance boundaries, since this is exactly the kind of unexpected behavior that can occur.