0

Is it possible to override a method implementation by deriving from an interface which gives a default implementation for that method? If not, how should I restructure my code to avoid explicit method calls to the interface's implementation?

Here is an overview of the structure of classes in my project:

interface IInputProcessor
{
    public ProcessInputsResult ProcessInputs(Item[] inputs);
}

abstract class MachineComponent : IInputProcessor
{
    public abstract ProcessInputsResult ProcessInputs(Item[] inputs);
}

interface IComponentContainer<T> where T : MachineComponent
{
    public T[] Components { get; }
}

class MachineSystem : MachineComponent, IComponentContainer<MachineComponent>
{
    public MachineComponent[] Components { get; }
}

My goal is to provide an implementation for ProcessInputs inside the IComponentContainer interface, since all component containers must process the inputs of each of their components, and the implementation for this can be the shared across the deriving classes.

To achieve this, I tried creating a default method implementation for ProcessInputs inside IComponentContainer, like so:

interface IComponentContainer<T> : IInputProcessor where T : MachineComponent
{
    public T[] Components { get; }

    ProcessInputsResult IInputProcessor.ProcessInputs(Item[] inputs)
    {
        ...
    }
}

Notice how I also made IComponentContainer derive from IInputProcessor, since all component containers have to process inputs for all their components.

I was hoping that this new definition would mean that MachineSystem, which is both a MachineComponent and a IComponentContainer<MachineComponent>, would not need to specify an implementation for MachineComponent.ProcessInputs, since this is given in the IComponentContainer interface.

This was not the case, and I had to explicitely refer the method to the implementation given in the interface like this:

class MachineSystem : MachineComponent, IComponentContainer<MachineComponent>
{
    ...

    public override ProcessInputsResult ProcessInputs(Item[] inputs) =>
        ((IComponentContainer<MachineComponent>)this).ProcessInputs(inputs);
}

I think this defeats the purpose of providing the default implementation in the interface as deriving classes have to specify that they want to use it.

Is there a good solution for this problem, or should I change the structure of the code?

UPDATE: In response to Ivan Petrov's answer

interface IComponentContainer<T> : IInputProcessor where T : MachineComponent {
    public T[] Components { get; }
}

class ComponentContainer<T> : MachineComponent, IComponentContainer<T> 
where T : MachineComponent {
    public T[] Components { get; }
    public override ProcessInputsResult ProcessInputs(Item[] inputs) {
    // implementation will serve for interface method too
    }
}

class MachineSystem : ComponentContainer<MachineComponent> {
    // we can still override ProcessInputsResult here too
}

Using a shared base class for the implementation of component container processing wouldn't work in my situation because the classes implementing IComponentContainer, all deriving from the abstract class MachineComponent, are at different levels in the class hierarchy. For example, MachineSystem : IComponentContainer<MachineComponent> inherits MachineComponent directly, whereas CompositeComponent : IComponentContainer<SimpleComponent> inherits another class SimpleComponent which in turn is derived from MachineComponent. For this reason using a single base class that all of these derive from would impose the same hierarchy on all of them. This could be resolved easily if generic inheritance types were possible, like this:

class ComponentContainer<TContainer, TBase> : TBase, IComponentContainer<TContainer> 
where TContainer : MachineComponent 
where TBase : MachineComponent 
{
    public T[] Components { get; }
    public override ProcessInputsResult ProcessInputs(Item[] inputs) 
    {
        ...
    }
}

But since this is not supported I don't see a way to make it work with a shared base class.

6
  • are MachineSystemComponent and MachineComponent the same thing, if not what is the relationship? Commented Jul 3 at 19:00
  • I can only confirm that I ended up in the same kind of class structure with public methods explicitly delegating to private methods of default interface implementations with the same name and signature and I wondered why I need that dual declaration and that it would be much nicer if the default impl would be available implicitly. It kinda makes this feature cumbersome.
    – lidqy
    Commented Jul 3 at 19:05
  • 1
    @Ivan Petrov Yes, I simplified the original name (MachineSystemComponent) for clarity. I've updated the question and removed the old name. Commented Jul 3 at 19:06
  • @EliaGiaccardi are you sure this really works (not just compiles) public override ProcessInputsResult ProcessInputs(Item[] inputs) => ((IComponentContainer<MachineComponent>)this).ProcessInputs(inputs); Commented Jul 3 at 19:33
  • @Ivan Petrov Unfortunately I don't have an easy way to test it right now, any specific concerns? Commented Jul 3 at 19:38

1 Answer 1

0

I think this defeats the purpose of providing the default implementation in the interface as deriving classes have to specify that they want to use.

Deriving (or interface implementing) classes don't have to specify anything if they didn't have an abstract class that made them do it.

but in our case we do have such a class

abstract class MachineComponent : IInputProcessor
{
    public abstract ProcessInputsResult ProcessInputs(Item[] inputs);
}

Now all derived classes (if not abstract themselves) need to override the method and thereby provide an implementation that will hide/have precedence over the default interface implementation of the interface at the same time.

Your workaround illustrates this

public override ProcessInputsResult ProcessInputs(Item[] inputs) =>
    ((IComponentContainer<MachineComponent>)this).ProcessInputs(inputs);

This casted method call compiles to the following IL:

callvirt instance class ProcessInputsResult IInputProcessor::ProcessInputs(class Item[])

We are making a virtual call to IInputProcessor::ProcessInputs from within the most specific/derived implementation of IInputProcessor::ProcessInputs. It will dispatch to itself causing an endless recursion.

This leads us to the realization that we cannot reuse the default interface implementation in a method that is supposed to override it the same we could have with calling base.BaseClassMethod in a virtual overriding method in class inheritance.

My goal is to provide an implementation for ProcessInputs inside the IComponentContainer interface, since all component containers must process the inputs of each of their components, and the implementation for this can be the shared across the deriving classes.

I think by adding an (abstract) class in the middle that implements IComponentContainer<T> and provides the implementation, you would achieve your goal.

interface IComponentContainer<T> : IInputProcessor where T : MachineComponent {
    public T[] Components { get; }
}

class ComponentContainer<T> : MachineComponent, IComponentContainer<T> 
where T : MachineComponent {
    public T[] Components { get; }
    public override ProcessInputsResult ProcessInputs(Item[] inputs) {
    // implementation will serve for interface method too
    }
}

class MachineSystem : ComponentContainer<MachineComponent> {
    // we can still override ProcessInputsResult here too
}
3
  • Thanks for the detailed answer. This can't be applied in my situation because classes that implement IComponentContainer (all derived from the abstract class MachineComponent) are at different levels in the hierarchy, so the base class ComponentContainer can't be reused as it always derives from MachineComponent. I'll update my question to include this Commented Jul 3 at 22:37
  • @EliaGiaccardi I think the rest of my answer did give you some clarity of why what you want to do with default interface implementations is not really possible with your current class structure. I won't be editing my answer, feel free to accept at some point if nothing better surfaces. Commented Jul 3 at 22:48
  • Absolutely, thank you for your help. I think I'll create a static method somewhere to give a basic implementation of input processing on child components and manually call it on component containers such as MachineSystem. This will also make it clearer that there is room for a different implementation since the classes still have to override the abstract method explicitly Commented Jul 3 at 23:01

Not the answer you're looking for? Browse other questions tagged or ask your own question.