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.
> 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 :)
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.
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
> 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 > 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?
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: > 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.
No, the call is ambiguous and results in a compiler error.
> 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 }
On May 30, 12:05 pm, "Jon Skeet [C# MVP]" <s...@pobox.com> wrote:
> 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.
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).
> 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) > }
This are possibly ambiguous members, they should be avoided but are permitted
> 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)"); > } > }
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 > { > 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);
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
> 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. ...."