.NET Forum / Languages / C# / May 2007
Overloading in generic interfaces
|
|
Thread rating:  |
gleb.alexeev@gmail.com - 30 May 2007 09:40 GMT Hello everyone!
It's my first post here. Could anyone please explain me the behaviour of the following code? <code> interface IFoo<T, P> { void Bar(T t, P p); // (1) void Bar(P p1, P p2); // (2) }
class NastyFooImpl : IFoo<int, int> { void IFoo<int, int>.Bar(int t, int p) { Console.WriteLine("void IFoo<int, int>.Bar(int t, int p)"); }
public void Bar(int p1, int p2) { Console.WriteLine("public void Bar(int p1, int p2)"); } }
class Program { static void Test<Foo, T, P> () where Foo : IFoo<T, P>, new() { Foo foo = new Foo(); T t = default(T); P p = default(P); foo.Bar(t ,p); foo.Bar(p, p); } static void Main(string[] args) { Test<NastyFooImpl, int, int>(); } } </code>
The output I'm getting using Visual Studio 2005: <code> void IFoo<int, int>.Bar(int t, int p) public void Bar(int p1, int p2) </code>
When I swap lines (1) and (2), the order of lines of output changes as well. Any links are greatly appreciated, especially links to C# specification.
Jon Skeet [C# MVP] - 30 May 2007 10:05 GMT On May 30, 9:40 am, gleb.alex...@gmail.com wrote:
> It's my first post here. > Could anyone please explain me the behaviour of the following code? <snip>
Yowser, that's horrible!
I'll look at the spec closely tonight and see whether it helps.
The fact that (as you point out) the behaviour changes depending on interface declaration order suggests something very nasty. It feels like it shouldn't compile, but I wouldn't like to say exactly where ;)
Has this come up as an issue in production code? If so, regardless of whether it's officially legal or what the compiler should really be doing, I'd suggest trying to refactor things to avoid the situation - you really don't want to have whoever reads the code next to have to worry about it :)
Jon
gleb.alexeev@gmail.com - 30 May 2007 11:29 GMT > Yowser, that's horrible! I agree.
> I'll look at the spec closely tonight and see whether it helps. Thanks, I'm looking forward to it.
> Has this come up as an issue in production code? Well I have use-case for instantiating generic interface with equal types, but I don't really need explicit interface implementation like in the code snippet I posted. I've encountered this dark corner of the language by accident. The purpose of this thread is to shed some light on this issue, regardless of whether tricks like this should be used in production.
Marc Gravell - 30 May 2007 11:50 GMT I've looked at the spec. The way I read 20.4.2, this is possibly a compiler bug, and "void IFoo<int, int>.Bar(int t, int p) " should actually implement *both* IFoo methods. It certainly mentions nothing about member sequence being significant. Generics don't mention any real changes (25.3.2).
Marc
Marc Gravell - 30 May 2007 10:34 GMT I'm not sure what you expect?
Overloading is determined at compile time, and for generics note that the overloads are fixed in *generic* terms, not for specific types;
As such, given "T t" & "P p", the only match on Bar(t,p) is via IFoo<T,P>.Bar(t, p), and the only match on Bar(p,p) is via IFoo<T,P>.Bar(p1, p2); because this is decided early for *all* P, T, there isn't any ambiguity here at all. If, however, you close the generic sooner (to int, int), then there *would* be an ambiguity:
IFoo<int, int> foo = new NastyFooImpl(); foo.Bar(5,6); // which to call?
I believe the spec mentions a related case in particular, but I can't find the reference. It was something like: class SomeType<T> { this[int index] {} this[T key] {} } then refererring to SomeType<int> by an int indexer. IIRC the "index" version wins over the "key" version because it checks non-generic members first.
But as Jon said: yowser! Is this purely academic?
Marc
Marc Gravell - 30 May 2007 10:41 GMT Retraction; I misread the 2 lines that (when swapped) changed the behaviour. It looks like some freakery in how the interface implementation members are ticked off... very freaky! This looks very much like an edge case, but very very curious. And one of the rare occasions we get to talk about pure C# (rather than patterns, CLR, etc) ;-p
Marc
Christof Nordiek - 30 May 2007 11:01 GMT > I'm not sure what you expect? > > Overloading is determined at compile time, and for generics note that > the overloads are fixed in *generic* terms, not for specific types; But the example of the OP is about interface mapping.
> As such, given "T t" & "P p", the only match on Bar(t,p) is via > IFoo<T,P>.Bar(t, p), and the only match on Bar(p,p) is via [quoted text clipped - 4 lines] > IFoo<int, int> foo = new NastyFooImpl(); > foo.Bar(5,6); // which to call? That is ambiguous and throws an compilererror. None of the two members can be called on an expression of type IFoo<int, int>.
> I believe the spec mentions a related case in particular, but I can't > find the reference. It was something like: [quoted text clipped - 5 lines] > version wins over the "key" version because it checks non-generic > members first. No, the call is ambiguous and results in a compiler error.
Christof
Marc Gravell - 30 May 2007 11:14 GMT > But the example of the OP is about interface mapping. Yes, see my retraction; now, I don't claim to be a .Net king, but I'm not a slouch either - and it threw me off the scent, which to my mind is a warning sign "don't do this even if it does work" ;-p
[re similar case as cited]
> No, the call is ambiguous and results in a compiler error. Not so; try this:
public class SomeDictionary<TKey> { public string this[int index] { get { return "index"; } } public string this[TKey key] { get { return "key"; } } }
static class Program { static void Main(string[] args) { SomeDictionary<int> wossit = new SomeDictionary<int>(); string called = wossit[3]; // returns "index" as claimed } }
Marc Gravell - 30 May 2007 11:23 GMT > Not so; try this: ref ECMA 334, 3rd edtion; 14.4.2.2 Better function member
"If one of MP and MQ is non-generic, but the other is generic, then the non-generic is better."
Christof Nordiek - 30 May 2007 12:27 GMT >> Not so; try this: > > ref ECMA 334, 3rd edtion; 14.4.2.2 Better function member > > "If one of MP and MQ is non-generic, but the other is generic, then > the non-generic is better." Actually, your conclusion is right. But it's the wrong rule. A generic member is a member that has itself type parameters, not a method that has a type parameter as a type of its parameters.
Applicable is the rule of more specific parameter types:
"Otherwise, .... if one method has more specific parameter types, then that method is better. ..... A type parameter is less specific than a non-type parameter. ...."
Christof
Marc Gravell - 30 May 2007 12:34 GMT > Actually, your conclusion is right. But it's the wrong rule. Fair enough; good catch.
Marc
Christof Nordiek - 30 May 2007 11:50 GMT > Hello everyone! > [quoted text clipped - 6 lines] > void Bar(P p1, P p2); // (2) > } This are possibly ambiguous members, they should be avoided but are permitted
> class NastyFooImpl : IFoo<int, int> > { [quoted text clipped - 9 lines] > } > } The question is, wich method implements wich interface member. The first implementation fits for both. There doesn't seem to be a rule wich permits one explicit interface implementation to be valid for two or more member. The second method also fits for both, but explicit interface implementations take precedence over implicit interface implementations. Both interface members should map on the first member.
> class Program > { [quoted text clipped - 5 lines] > foo.Bar(t ,p); > foo.Bar(p, p); Here the both members are unambiguous. The first statement calls the first member, the second calls the second member. Wich implementations are called depends of the interface
> } > static void Main(string[] args) [quoted text clipped - 9 lines] > public void Bar(int p1, int p2) > </code> This would be a bug, since both should call the explicit implementation.
> When I swap lines (1) and (2), the order of lines of output changes as > well. Also a bug.
Christof
Marc Gravell - 30 May 2007 12:31 GMT > There doesn't seem to be a rule wich > permits one explicit interface implementation to be valid for two or more > member. But equally, I can't find one which expressly denies this (it may be there, but if so I can't see it). Likewise, there is no syntax for explicit disambiguation. Prior to generics it was a non-issue as you couldn't duplicate the signature within a single interface (the spec [as we all know] allows you to disambiguate between signature-equivalent members on different interfaces).
A specification failure, perhaps then...?
Marc
gleb.alexeev@gmail.com - 30 May 2007 12:54 GMT > A specification failure, perhaps then...? Marc and Christof, thanks a lot! I had a feeling that this looked like a bug in the spec, but I'm not a C# expert.
Christof Nordiek - 30 May 2007 13:06 GMT >> There doesn't seem to be a rule wich >> permits one explicit interface implementation to be valid for two or more >> member. > > But equally, I can't find one which expressly denies this (it may be > there, Oops, that's what I wanted to say, "wich forbids" not "wich permits".
Christof
Jon Skeet [C# MVP] - 30 May 2007 19:19 GMT <snip>
> When I swap lines (1) and (2), the order of lines of output changes as > well. > Any links are greatly appreciated, especially links to C# > specification. Leaving the spec itself aside, I'm not sure this isn't actually a CLR issue. The IL generated for the calls themselves is the same whichever way round the interface is declared:
callvirt instance void class IFoo`2<!!T,!!P>::Bar(!0, !1) ... callvirt instance void class IFoo`2<!!T,!!P>::Bar(!1, !1)
So I think it's the CLR resolution which is causing the massive oddity.
I still haven't looked in detail at what the spec says. I'll mail Eric Lippert later on to see what he makes of it.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Marc Gravell - 30 May 2007 20:13 GMT But since this is in a generic method, this is what we expect, isn't it (detailed as per my first post in this chain)? I'd be looking more at the class defs?
I like the 3.0 warning, though ;-p
Marc Gravell - 30 May 2007 21:02 GMT Have looked at the IL - looks like no significant difference; sorry Jon - you were right ;-p
Ben Voigt [C++ MVP] - 30 May 2007 23:18 GMT > <snip> > [quoted text clipped - 5 lines] > Leaving the spec itself aside, I'm not sure this isn't actually a CLR > issue. The IL generated for the calls themselves is the same whichever It's not the calls that have unusual behavior, that's well defined. It's the override behavior during implementation.
> way round the interface is declared: > [quoted text clipped - 6 lines] > I still haven't looked in detail at what the spec says. > I'll mail Eric Lippert later on to see what he makes of it. Jon Skeet [C# MVP] - 30 May 2007 23:29 GMT > > Leaving the spec itself aside, I'm not sure this isn't actually a CLR > > issue. The IL generated for the calls themselves is the same whichever > > It's not the calls that have unusual behavior, that's well defined. It's > the override behavior during implementation. What exactly do you mean? I'm afraid I didn't fully follow either sentence. Note that the C# 3.0 compiler warns that the behaviour *isn't* specified.
Are you suggesting that the CLR is buggy but that the C# specification *does* say which version should be called, or that the C# compiler is definitely generating the right calls, but how those calls are resolved is undefined?
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Christof Nordiek - 31 May 2007 07:44 GMT > <snip> > [quoted text clipped - 10 lines] > ... > callvirt instance void class IFoo`2<!!T,!!P>::Bar(!1, !1) But that is the call of the interface members. Each of them choses the right interface member. The problem is, how the interface member is maped to the methods. The calling occurs on a constructed class, where the two overloads can clearly ditinguished. The interface mapping for NastyFooImpl occurs on a constructed type, where the members can't be distinguished. We'de have to look how the interface mapping is defined in the IL to see, if the C# compiler or the CLR makes the decission.
Christof
Jon Skeet [C# MVP] - 31 May 2007 08:32 GMT <snip>
> But that is the call of the interface members. Each of them choses the right > interface member. The problem is, how the interface member is maped to the > methods. Agreed.
> The calling occurs on a constructed class, where the two overloads can > clearly ditinguished. > The interface mapping for NastyFooImpl occurs on a constructed type, where > the members can't be distinguished. We'de have to look how the interface > mapping is defined in the IL to see, if the C# compiler or the CLR makes the > decission. I've made what I think is a fairly important discovery, in terms of the difference that is made by reordering the interface members. One way round, the IL for the explicit implementation is: .override method instance void class IFoo`2<int32,int32>::Bar(! 1, !1)
The other way round, it's .override method instance void class IFoo`2<int32,int32>::Bar(! 0, !1)
In other words it's explicitly implementing different methods, which explains everything else, I think. That would put the ball in the C# compiler's court (rather than the CLR's) but it doesn't answer whether it's a C# compiler *bug* or a problem with the spec.
Jon
Jon Skeet [C# MVP] - 30 May 2007 19:28 GMT > It's my first post here. > Could anyone please explain me the behaviour of the following code? <snip>
The C# 3.0 compiler can explain it for you, now I've tried it:
Test.cs(11,10): warning CS0473: Explicit interface implementation 'IFoo<...>.Bar' matches more than one interface member. Which interface member is actually chosen is implementation-dependent. Consider using a non-explicit implementation instead.
So at least in the future you'll be warned about such things.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Christof Nordiek - 31 May 2007 07:51 GMT > The C# 3.0 compiler can explain it for you, now I've tried it: > > Test.cs(11,10): warning CS0473: Explicit interface implementation > 'IFoo<...>.Bar' matches more than one interface member. Which > interface member is actually chosen is implementation-dependent. > Consider using a non-explicit implementation instead. Still, from the specs, the implementation should map an both members. So it's not implementation defined. Also, the distinction can't be made by using non-explicit implementation.
But maybe, there is a change in the interface mapping rule between C#2.0 and C#3.0
Christof
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|