Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making member virtual prevents calling default interface implementation and causes StackOverflowException in C# 8

Consider the code:

class ChildClass : BaseClass {

    public void Method1() {} //some other method

}

abstract class BaseClass : IChildInterface {

    public
    virtual //<- If we add virtual so that this method can be overridden by ChildClass, we get StackOverflowException and DoWork() implementation in IChildInterface is never called.
    void DoWork() {
     //base class specific implmentation
     ((IChildInterface)this).DoWork(); //call into default implementation provided by IChildInterface
    }

}

interface IChildInterface : IBaseInterface {

    void IBaseInterface.DoWork() {
     //implmentation
    }

}

interface IBaseInterface {

    void DoWork();

}

The problem is that if we mark DoWork() in BaseClass as virtual so that it can be overridden by child classes, it prevents it from calling into IChildInterface's default implementation of DoWork(), causing StackOverflowException.

If we remove virtual modifier from DoWork() in the BaseClass, everything works and the IChildInterface's default implementation of DoWork() is called.

Is such a behavior a bug, or by design?

Is there a way to make it possible for some child classes provide their own implementation of DoWork() (thus overriding BaseClass's implementation) but still being able to use IChildInterface's default implementation of DoWork()?

like image 217
Fit Dev Avatar asked Dec 18 '19 07:12

Fit Dev


People also ask

What is default interface implementation C#?

Default interface methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface. The feature enables C# to interoperate with APIs targeting Android (Java) and iOS (Swift), which support similar features.

Can interface have implementation in C#?

With C# 8.0, you can now have default implementations of methods in an interface. Interface members can be private, protected, and static as well. Protected members of an interface cannot be accessed in the class that extends the interface.

Can interface have virtual methods in C#?

Yes, interface implementation methods are virtual as far as the runtime is concerned. It is an implementation detail, it makes interfaces work. Virtual methods get slots in the class' v-table, each slot has a pointer to one of the virtual methods.

Can interface have private methods C#?

An interface has only public members and it cannot include private, protected, or internal members. An interface cannot contain fields.

Are implementation methods of an interface Virtual by default?

Interface is not Base Class, so implementation methods are not overriden. Interface only declares the methods, Interface methods are not virtual by default, infact interfaces only declare the methods that are available on the class that implements that interface. Declaration can not be virtual.

What does it mean when an interface member is marked virtual?

Virtual in the IL sense means "it's called indirect" via a VMT (virtual method table), that is about the same mechanism for class overriding and interface implementation. In the IL sense, an interface member must be marked virtual - there is no way around. But if you look in the implementation, it's marked as 'virtual final'.

How to call a different implementation of an interface member explicitly?

To call a different implementation depending on which interface is in use, you can implement an interface member explicitly. An explicit interface implementation is a class member that is only called through the specified interface.

Can interface members be specified with code body?

An interface member can now be specified with a code body, and if an implementing class or struct does not provide an implementation of that member, no error occurs. Instead, the default implementation is used.


2 Answers

You're calling BaseClass.DoWork recursively which, if you're lucky, will result in a StackOverflowException. If the call was the last one in the method, you'd get an infinite recursion due to tail call optimizations. You'd end up with a core stuck at 100% until you killed the app.

This code :

public virtual void DoWork() {
   ((IChildInterface)this).DoWork(); by IChildInterface
}

Is identical to :

//That's the actual implementation of the interface method
public virtual void DoWork() {
     DoWork(); 
}

The virtual keyword doesn't matter. You'd still get infinite recursion without it. Whether it exists or not, this line throws a StackOverflowException after a while :

new ChildClass().DoWork();

When you implemented BaseClass.DoWork that became the single implementation available to everyone, unless overridden by a child class.

Interfaces are not abstract classes, even in C# 8. A default method implementation is not an actual method. As the name says, it's a default implementation. It's used when there's no better implementation available. You can't call the default implementation when the method is already implemented in a class.

In fact, in almost every case you wouldn't expect the default method to be called. DIMs are called explicitly through the interface, the same way explicit interface implementations are used. Callers of the method expect the most-derived implementation to run, not the base or mid-level one.

Besides, even on previous C# versions you wouldn't expect casting to an interface to change which method is actually called. You'd expect that only with classes. To call a base class implementation you'd use the base keyword. The base class of BaseClass though is Object which doesn't have a DoWork method.

If you used :

void DoWork() {
    base.DoWork(); 
}

You'd get a CS0117: 'object' does not contain a definition for 'DoWork'

Update

The C# design team has already though about this. This couldn't be implemented efficiently without runtime support and was cut i May 2019. Runtime optimizations is what makes DIM calls as cheap as other calls, without boxing etc.

The proposed syntax is a base(IMyInterface) call :

interface I1
{ 
    void M(int) { }
}

interface I2
{
    void M(short) { }
}

interface I3
{
    override void I1.M(int) { }
}

interface I4 : I3
{
    void M2()
    {
        base(I3).M(0) // What does this do?
    }
}
like image 87
Panagiotis Kanavos Avatar answered Oct 17 '22 16:10

Panagiotis Kanavos


As all methods inside interfaces are virtual by default the DoWork is virtual inside every each of these definitions/implementations you provided except the ChildClass. When you explicitly use DoWork of IChildInterface it uses BaseClass.DoWork implicitly which then uses ((IChildInterface)this).DoWork(); explicitly again. And so on. You have this loop that is never ending, hence you're getting the StackOverflow.

like image 26
Gleb Avatar answered Oct 17 '22 14:10

Gleb