dotnet / dotnet/csharpstandard
Should the spec explain interface mapping for variant interfaces?
- Dominant language
- C#
- Stars
- 815
- Forks
- 99
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 16
Description
Consider the following code (especially notice that the interface is covariant):
```c#
interface I { void M(); }
class A1 {}
class A2 : A1 {}
class B1 : I
{
void I.M() => Console.WriteLine("B1.I.M");
}
class B2 : B1, I
{
void I.M() => Console.WriteLine("B2.I.M");
}
…
I x = new B2();
x.M();
```
This code prints `B2.I.M`, but I could not find justification for that behavior in the C# spec.
When the above code is compiled, [it produces the expected IL](https://sharplab.io/#v2:C4LglgNgPgAgDAAhgRgNwFgBQWwDtgCmATgGYCGAxgQgJIA8A9gK7AIAqAfAgN5IAsCALIAKAJSoEAXyxYYAJgQBBZD2mZ5ShSCUrua2QoBCK7fWUcs3LAhv9adcwDoRohAF4uKAJzCARMcczZA5nX3EsfXUjLQRjABp7RTkLTCtMWzszZOcxd09kH385QIdswTCMTEiUADYkBRQAdktrW1q7F1abNIyMoK4AD3cEXAIAd1i5MUqMgEgBnPD02zVJIA=), where `I.M` contains an `.override` for `I`.
When executed on the CLR, it behaves according to [ECMA-335 §II.12.2 Implementing virtual methods on interfaces](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf#%5B%7B%22num%22%3A1997%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C87%2C257%2C0%5D), which says (emphasis mine):
> When an interface method is invoked, the VES shall use the following algorithm to determine the
appropriate method to call:
> * Beginning with the runtime class of the instance through which the interface method
is invoked, using its interface table as constructed above […]:
> 1. For each method in the list associated with the interface method, if there exists a
method whose generic type arguments match exactly for this instantiation (or
there are no generic type parameters), then call the first method. […]
> 2. **Otherwise, if there exists a method in the list whose generic type parameters
have the correct variance relationship, then call the first such method in the list.**
> 3. If no method is found in this class, return to step 1 with the next class in the
inheritance chain (i.e. the Extends field of the current class)
> 4. If no method is found, then raise System.InvalidCastException
But [a similar section of ECMA-334, §18.6.5 Interface mapping](https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf#%5B%7B%22num%22%3A2942%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C55%2C737%2C0%5D) says:
> The implementation of a particular interface member `I.M`, where `I` is the interface in which the member `M` is declared, is determined by examining each class or struct `S`, starting with `C` and repeating for each successive base class of `C`, until a match is located:
> * If `S` contains a declaration of an explicit interface member implementation that matches `I` and `M`, then this member is the implementation of `I.M`.
> * Otherwise, if `S` contains a declaration of a non-static public member that matches `M`, then this member is the implementation of `I.M`. If more than one member matches, it is unspecified which member is the implementation of `I.M`. This situation can only occur if `S` is a constructed type where the two members as declared in the generic type have different signatures, but the type arguments make their signatures identical.
>
> […]
>
> For purposes of interface mapping, a class or struct member `A` matches an interface member `B` when:
> * `A` and `B` are methods, and the name, type, and formal parameter lists of `A` and `B` are identical.
> * […]
It's not quite clear to me what the above part of the spec says about the code in the example, because I think it doesn't define what matching for explicit interface members means.
I think the spec should be clarified, so that it properly explains how the above code should behave.
Contributor guide
Assessment
This issue has not been assessed yet.