4

I am new to C# and I don't understand why compiler does not complain on this code. Here is the hierarchy of classes:

interface IAble
{
    void f();
}

class AAble : IAble
{
    public void f()
    {
        Debug.Log("---->> A - Able");
    }
}

class BAble : AAble
{
    public void f()
    {
        Debug.Log("---->> B - Able");
    }
}

execution code:

        IAble i = new BAble();
        i.f();

On execution ---->> A - Able was printed. Why? How the compiler knows what function should be called?

When the decision is made of what function to call - runtime or compile time? What if I defile a new class class CAble : IAble?

Narek
  • 38,779
  • 79
  • 233
  • 389
  • Possible duplicate of [What is the difference between method hiding and shadowing in C#?](https://stackoverflow.com/questions/9520226/what-is-the-difference-between-method-hiding-and-shadowing-in-c) – Code Name Jack Sep 06 '18 at 11:53
  • 4
    do you read compiler warning? – Selman Genç Sep 06 '18 at 11:54
  • to understand this behavior , i recommend to do the following: make AAble -> virtual void f() and BAble -> override void f(). You will get exactly what you expected. Compare two solutions : hidden method and overridden method – Z.R.T. Sep 06 '18 at 12:57

4 Answers4

2

Because AAble is implementing the IAble interface, its AAble.f is marked as the implementation of the IAble.f method for type AAble.

BAble.f is simply hiding the AAble.f method, it is not overriding it.

IAble o = new BAble(); o.f(); // calls AAble.f
AAble o = new BAble(); o.f(); // calls AAble.f
BAble o = new BAble(); o.f(); // calls BAble.f
IAble o = new CAble(); o.f(); // calls CAble.f

The decision is made at compile-time:

// AAble.f in IL:
.method public final hidebysig newslot virtual 
    instance void f () cil managed 

// BAble.f in IL:
.method public hidebysig 
    instance void f () cil managed

Interface implementations are marked as virtual in IL, even though it wasn't marked virtual in C#. The method is also marked as final in IL, if the method would've been virtual in C#, it would not have been marked as final.

ikkentim
  • 1,639
  • 15
  • 30
  • This makes the most sense how the dispatching mechanism happens, thanks for the IL code. – Narek Sep 07 '18 at 05:38
1

Normally there would be a compiler warning about this as it's hiding a method. but within C# it's legal to do for non-virtual functions. However of course, if it were a virtual function then obviously the B version of the method would run.

Because you declare it as an IAble and it's non-virtual, the compiler reads it as an IAble. If it were declared virtual, the compiler would scan through the hierarchy of inheritance and would see that it's actual class is a BAble and it would run the BAble code.

Prodigle
  • 1,757
  • 12
  • 23
  • 1
    "the compiler would scan through the hierarchy of inheritance" -> that's actually not true. The lookup happens at runtime, and it's just a lookup, no "scanning". See https://www.google.com/search?q=virtual+method+table – Kris Vandermotten Sep 06 '18 at 12:41
  • Thanks :) I knew c++ compiled it that way but thought I'd heard different of C# – Prodigle Sep 06 '18 at 12:43
1

When you define method in derived class that has the same signature as in the base class, you are hiding it.

It means when you declare variable with base type and initialize it with dervied type, then method from the base class will be used. That's what hapenning in your code.

More general: when you hide methods, then version of a method that will be used, cmoes from the class that you declared it with.

So if you had another class CAble and used like:

BAble c = new CAble();
b.f();

then the result would be ---->> B - Able.

In your case, you declare variabble as IAble. It doesn't have implementation, so it looks at implementation, which is defined in class AAble. Other classes only hide the method.

In order to hide method, you can specify both methods with the same signature. But you should always use new keywrods, to explicitly hide the method (which will indicate, that hiding was on purpose).

What you expect is overriding methods, accomplshed by using override keywaord, when defining a method.

In order to override method, it should be marked as virtual (if it has implementation) or abstract (if it doesn't have implementation) in a base class.

Michał Turczyn
  • 32,028
  • 14
  • 47
  • 69
  • I know about virtual methods and overriding. The problem is that if I will add a new class say `class CAble : IAble` then `IAble` will have two direct children. How the compiler will chose which implementation to call? I just told the compiler that my object is IAble and it did select some implementation. How? – Narek Sep 06 '18 at 12:01
  • @Narek As I said, "first" implementation (from the inheritanca point of view) will be called, when hidden. Method that hide the other method will never be called, unless the variable is **initialized with the type where you hid the method**. – Michał Turczyn Sep 06 '18 at 12:04
  • @Narek That's because it has a runtype type. At runtime, it _is_ CAble or AAble or whatever. All the Interface is doing is making sure the client will be given a runtime type that has a method with a specific signature. – Fildor Sep 06 '18 at 12:04
  • @MichałTurczyn where did you mention that **"first" implementation (from the inheritanca point of view) will be called**? Also the decision of which function to call is made runtime in this case or compile time? – Narek Sep 06 '18 at 12:10
  • @Narek - an interface doesn't have "children". An interface is not an abstract class. An interface is a contract that descibes certain "abilities" (properties, methods, etc.). Concrete classes may *implement* ("fullfill") one or possibly several of these contracts. When you declare a variable or parameter as `IAble something`, you say "I don't care about the concrete class, I just want *something* that can do X". – Corak Sep 06 '18 at 12:12
  • @Narek See updated asnwer. It indicates also answer for your new question with additional class. – Michał Turczyn Sep 06 '18 at 12:27
1

Interface must be implemented in a class that inherits from it directly and not in one of derived classes. For instance this code won't compile:

class AAble : IAble
{
    public void f() { ... }
}

class BAble : AAble
{
    // An attempt to explicitly implement interface in BAble through AAble class
    void IAble.f()
    {
        Console.WriteLine("---->> B - Able");
    }
}

When we upcast BAble to interface IAble an implementation from AAble is used as it is the only class from compile prospective that implement the interface.

We could inherit from interface directly and this will tell compiler that which implementation of interface should be used:

class BAble : AAble, IAble
{
    // Now it compiles
    void IAble.f()
    {
        Console.WriteLine("---->> B - Able");
    }
}

Output: ---->> B - Able"

Or we could use polymorphism. This will tell compiler to always use the overridden function:

class AAble : IAble
{
    public virtual void f()
    {
        Debug.Log("---->> A - Able");
    }
}

class BAble : AAble, IAble
{
    public override void f()
    {
        Console.WriteLine("---->> B - Able");
    }
}
Fabjan
  • 13,506
  • 4
  • 25
  • 52