Can someone please help me understand why this code snippet returns "Bar-Bar-Quux"? I'm having a hard time understanding this even after reading up on interfaces.
interface IFoo
{
string GetName();
}
class Bar : IFoo
{
public string GetName() { return "Bar"; }
}
class Baz : Bar
{
public new string GetName() { return "Baz"; }
}
class Quux : Bar, IFoo
{
public new string GetName() { return "Quux"; }
}
class Program
{
static void Main()
{
Bar f1 = new Baz();
IFoo f2 = new Baz();
IFoo f3 = new Quux();
Console.WriteLine(f1.GetName() + "-" + f2.GetName() + "-" + f3.GetName());
}
}
Interfaces by definition have no associated implementation, which is to say their methods are always virtual and abstract. In contrast, the class
Bar
above defines a concrete implementation forGetName
. This satisfies the contract required to implementIFoo
.Class
Baz
now inherits fromBar
and declares anew
methodGetName
. That is to say that the parent classBar
has a method with the same name, but it is completely ignored when working withBaz
objects explicitly.However, if a
Baz
object is cast as aBar
, or simply assigned to a variable of typeBar
orIFoo
, it will do as it's told and behave like aBar
. In other words, the method nameGetName
refers toBar.GetName
instead ofBaz.GetName
.Now, in the third case,
Quux
both inherits fromBar
and implementsIFoo
. Now, when cast as anIFoo
it will provide its own implementation (according to the specification provided in Mike Z's answer).When a Quux is cast as a Bar, however, it returns "Bar", just as Baz does.
There are two things happening here. One is member hiding. This is fairly well-known and covered elsewhere. The other, less-known feature is interface re-implementation covered in section 13.4.6 of the C# 5 specification. To quote:
and
The result for
f1.GetName()
is "Bar" because the methodBaz.GetName
is hidingBar.GetName
andf1
is declared as typeBar
. There is no dispatch to the run-time type's implementation unless it is explicitly declared as virtual and overridden.Similarly, for
f2.GetName()
,Baz.GetName
is hiding the implementation inBar
, so it is not called when using dispatch through a reference to the interface. The interface is "mapped" to the method declared inBar
because that is the type on which the interface was declared. It does not matter thatBaz
has a compatible method with the same name. The rules for interface mapping are defined in section 13.4.4 of the spec. IfGetName
had been declared virtual inBar
, it could be overridden, which then would be called through the interface. The result is therefore also "Bar".For
f3.GetName()
,Quux
re-implementsIFoo
so it gets to define its own mapping toGetName
. Note that it also hides the implementation inherited fromBar
. It is not necessary to use new to do the re-implementation, it simply suppresses the warning about hiding. Therefore the result is "Quux".So that explains the output that you see: "Bar-Bar-Quux"
This post by Eric Lippert discuss some more nuances in this tricky feature.
The output is Bar-Bar-Quux as a result of 3 calls to GetName() in your Console.WriteLine method call.
Let's examine each call so it can be made more clear what happens.
f1.GetName()
f1
is instantiated asBaz
. However, it is typed asBar
. BecauseBar
exposesGetName
, whenf1.GetName()
is used, that is the method which is called - regardless of the fact thatBaz
also implementsGetName
. The reason is thatf1
is not typed asBaz
, and if it were, it would callBaz
'sGetName
method. An example of that would be to examine the output ofThis is possible because of two facts. First,
f1
was initially instantiated asBaz
, it was simply typed asBar
. Second,Baz
does have aGetName
method, and the use ofnew
in its definition hides the inheritedBar
'sGetName
method allowingBaz
'sGetName
to be called.f2.GetName()
A very similar typing is occurring with
f2
, which is defined asWhile the Baz class does implement a
GetName
method, it does not implementIFoo
'sGetName
method becauseBaz
does not inherit fromIFoo
and therefore the method is not available.Bar
implementsIFoo
, and sinceBaz
inherits fromBar
,Bar
'sGetName
is the method which is exposed whenf2
is typed asIFoo
.Again, since
f2
was initially instantiated asBaz
, it can still be cast toBaz
.And will have the same output result for the reason noted above for
f1
(f2
was originally typed asBaz
, andBaz
'sGetName
method hides the inheritedBar
'sGetName
method).f3.GetName()
Different story here.
Quux
does inherit and implementIFoo
, and it hidesBar
's implementation ofIFoo
by usingnew
. The result is thatQuux
'sGetName
method is the one which is called.