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

后端 未结 3 1224
不知归路
不知归路 2021-01-13 03:02

Consider the code:

class ChildClass : BaseClass {

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

}

abstract class         


        
相关标签:
3条回答
  • 2021-01-13 03:26

    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?
        }
    }
    
    0 讨论(0)
  • 2021-01-13 03:35

    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.

    0 讨论(0)
  • 2021-01-13 03:36

    For the sake of future readers...

    While the accepted answer provided by @Panagiotis is correct, in that there is no difference whether virtual modifier is present or not and StackOverflowExcpetion will occur in any case, I wanted to provide a concrete answer to the question that I settled on.

    The whole point of implementing DoWork() in the IChildInterface as opposed to in a class was for code reuse and staying "DRY". Classes that implement IChildInterface should however be able to add their own functionality on top of what's provided by IChildInterface.

    And therein lies a problem, as calling ((IChildInterface)this).DoWork(); from any class (abstract or not) that implements IChildInterface will result in infinite recursion. The only reasonable way out seems to use protected static members (as in fact is suggested in the Microsoft Docs):

    class ChildClass : BaseClass {
    
        public void Method1() {} //some other method
    
    }
    
    abstract class BaseClass : IChildInterface {
    
        public virtual void DoWork() {
         // Base class specific implementation here
    
         // Then call into default implementation provided by IChildInterface:
         // Instead of this: ((IChildInterface)this).DoWork();
         // Use static implementation:
         IChildInterface.DoWork(this);
        }
    
    }
    
    interface IChildInterface : IBaseInterface {
    
        protected static void DoWork(IChildInterface it){
          // Implementation that works on instance 'it'
        }
        void IBaseInterface.DoWork() => IChildInterface.DoWork(this);
    
    }
    
    interface IBaseInterface {
    
        void DoWork();
    
    }
    

    In the above solution we are staying "DRY" by still having a single (core) implementation of DoWork(), but it is located in a protected static member of the interface IChildInterface instead of being part of its inheritance hierarchy.

    Then, as far as the inheritance hierarchy is concerned, all interfaces / classes deriving from / implementing IChildInterface could simply use IChildInterface.DoWork(this) to access the default implementation. This applies to the IChildInterface itself.

    0 讨论(0)
提交回复
热议问题