Overloading in generic interfaces

G

gleb.alexeev

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.
 
J

Jon Skeet [C# MVP]

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
 
M

Marc Gravell

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
 
M

Marc Gravell

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
 
C

Christof Nordiek

Marc Gravell said:
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
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.

Christof
 
M

Marc Gravell

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
}
}
 
M

Marc Gravell

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."
 
G

gleb.alexeev

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.
 
M

Marc Gravell

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
 
C

Christof Nordiek

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)
}

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
}
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>

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
 
C

Christof Nordiek

Marc Gravell said:
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
 
M

Marc Gravell

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
 
M

Marc Gravell

Actually, your conclusion is right. But it's the wrong rule.

Fair enough; good catch.

Marc
 
G

gleb.alexeev

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.
 
C

Christof Nordiek

Marc Gravell said:
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
 
J

Jon Skeet [C# MVP]

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.
 
J

Jon Skeet [C# MVP]

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.
 
M

Marc Gravell

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
 
M

Marc Gravell

Have looked at the IL - looks like no significant difference; sorry
Jon - you were right ;-p
 
B

Ben Voigt [C++ MVP]

Jon Skeet said:
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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top