Why does this C# code return what it does
Asked Answered
A

3

37

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());
    }
}
Ambagious answered 29/7, 2014 at 3:57 Comment(5)
@Rahul It's Bar-Bar-Quux on my end (VS 2013 on Win7).Californium
Which part are you having trouble with? Why f1.GetName returns "Bar"? Why f2.GetName() returns Bar, or why f3.GetName() return Quux? The first one should be obvious. The 2nd one is a little tricky. The 3rd one probably requires a close reading of the spec to know what will happen without just trying it (for me anyway)Thermistor
@PeteBaughman, Yeah, my bad. didn't observe that Baz is not implementing IFoo. Removed comment.Castano
@Castano - Baz does implement IFoo through its inheritance of Bar.Multiple
@Enigmativity, I mean it's not explicitely implementing IFoo.Castano
P
57

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:

A class that inherits an interface implementation is permitted to re-implement the interface by including it in the base class list. A re-implementation of an interface follows exactly the same interface mapping rules as an initial implementation of an interface. Thus, the inherited interface mapping has no effect whatsoever on the interface mapping established for the re-implementation of the interface.

and

Inherited public member declarations and inherited explicit interface member declarations participate in the interface mapping process for re-implemented interfaces.

The result for f1.GetName() is "Bar" because the method Baz.GetName is hiding Bar.GetName and f1 is declared as type Bar. 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 in Bar, so it is not called when using dispatch through a reference to the interface. The interface is "mapped" to the method declared in Bar because that is the type on which the interface was declared. It does not matter that Baz has a compatible method with the same name. The rules for interface mapping are defined in section 13.4.4 of the spec. If GetName had been declared virtual in Bar, it could be overridden, which then would be called through the interface. The result is therefore also "Bar".

For f3.GetName(), Quux re-implements IFoo so it gets to define its own mapping to GetName. Note that it also hides the implementation inherited from Bar. 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.

Panjandrum answered 29/7, 2014 at 4:17 Comment(0)
C
8

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 for GetName. This satisfies the contract required to implement IFoo.

Class Baz now inherits from Bar and declares a new method GetName. That is to say that the parent class Bar has a method with the same name, but it is completely ignored when working with Baz objects explicitly.

However, if a Baz object is cast as a Bar, or simply assigned to a variable of type Bar or IFoo, it will do as it's told and behave like a Bar. In other words, the method name GetName refers to Bar.GetName instead of Baz.GetName.

Now, in the third case, Quux both inherits from Bar and implements IFoo. Now, when cast as an IFoo 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.

Californium answered 29/7, 2014 at 4:22 Comment(0)
H
4

The output is Bar-Bar-Quux as a result of 3 calls to GetName() in your Console.WriteLine method call.

Bar f1 = new Baz();
IFoo f2 = new Baz();
IFoo f3 = new Quux();
Console.WriteLine(f1.GetName() + "-" + f2.GetName() + "-" + f3.GetName());
//Bar-Bar-Quux

Let's examine each call so it can be made more clear what happens.

f1.GetName()

f1 is instantiated as Baz. However, it is typed as Bar. Because Bar exposes GetName, when f1.GetName() is used, that is the method which is called - regardless of the fact that Baz also implements GetName. The reason is that f1 is not typed as Baz, and if it were, it would call Baz's GetName method. An example of that would be to examine the output of

Console.WriteLine(((Baz)f1).GetName() + "-" + f2.GetName() + "-" + f3.GetName());
//Baz-Bar-Quux

This is possible because of two facts. First, f1 was initially instantiated as Baz, it was simply typed as Bar. Second, Baz does have a GetName method, and the use of new in its definition hides the inherited Bar's GetName method allowing Baz's GetName to be called.

f2.GetName()

A very similar typing is occurring with f2, which is defined as

IFoo f2 = new Baz();

While the Baz class does implement a GetName method, it does not implement IFoo's GetName method because Baz does not inherit from IFoo and therefore the method is not available. Bar implements IFoo, and since Baz inherits from Bar, Bar's GetName is the method which is exposed when f2 is typed as IFoo.

Again, since f2 was initially instantiated as Baz, it can still be cast to Baz.

Console.WriteLine(f1.GetName() + "-" + ((Baz)f2).GetName() + "-" + f3.GetName());
//Bar-Baz-Quux

And will have the same output result for the reason noted above for f1 (f2 was originally typed as Baz, and Baz's GetName method hides the inherited Bar's GetName method).

f3.GetName()

Different story here. Quux does inherit and implement IFoo, and it hides Bar's implementation of IFoo by using new. The result is that Quux's GetName method is the one which is called.

Haggi answered 30/7, 2014 at 18:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.