Is there a way to extend a method by automatically calling the base method without writing the call in C#?

Viewed 157

I wish to have a debug logger in a class that should be extended. Is there a way to do this without having to add the log entry on every derived class?

using System;

public abstract class Phase {
    public virtual void Start() {
        Console.Write("[LOG] Start");
    }

    /// <summary>
    /// Returns true when finished.
    /// </summary>
    public virtual bool Update() {
        Console.Write("[LOG] Update");
        return true;
    }

    public virtual void End() {
        Console.Write("[LOG] End");
    }
}
public class PhaseMenu : Phase {
    private byte _counter;

    public override void Start() {
        _counter = 0;
    }
    
    public override bool Update() {
        _counter++;
        if ( _counter > 100 ) {
            return true;
        }
        return false;
    }

    public override void End() {

    }
}

Desired outcome is to have the LOGs be written whenever PhaseMenu's methods are called. So that the methods aren't overriding but rather extending.

3 Answers

Reading your question and the relative comments, the only way out I can think of is implementing some kind of event based structure. This is way more than a hassle of just writing base.Log(), but it may do the thing for you.

Basically you can subscribe every method to an event or something like that and said event logs. That would do it automagically.

To add to that, the event subscription can be done programmatically, (through reflection or other means), so it would be a few lines of code.

EDIT: Here is a sample to work it out with reflection

@Dorjan maybe an idea as well:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");

        var phaseMenu = new PhaseMenu();
        
        phaseMenu.Start();
        phaseMenu.Update();
        phaseMenu.Update();
        phaseMenu.Update();
        phaseMenu.End();
    }
}

public abstract class Phase 
{
    private readonly Action _onStart;
    private readonly Func<bool> _onUpdated;
    private readonly Action _onEnd;

    protected Phase(Action aOnStart, Func<bool> aOnUpdated, Action aOnEnd)
    {
        _onStart = aOnStart;
        _onUpdated = aOnUpdated;
        _onEnd = aOnEnd;
    }
        
    public void Start() 
    {
        Console.WriteLine("[LOG] Start");
        _onStart?.Invoke();
    }
        
    public bool Update() 
    {
        Console.WriteLine("[LOG] Update");
        return _onUpdated?.Invoke() ?? false;
    }

    public void End() 
    {
        Console.WriteLine("[LOG] End");
        _onEnd?.Invoke();
    }
}
    
public class PhaseMenu : Phase 
{
    private static byte _counter;

    public PhaseMenu() : base(OnStart, OnUpdated, OnEnd)
    { }

    private static void OnStart()
    {
        _counter = 0;
    }
    
    private static bool OnUpdated()
    {
        _counter++;
        return _counter > 100;
    }
    
    private static void OnEnd()
    {
        Console.WriteLine("That is all folks!");
    }
}

Output:

Hello World!
[LOG] Start
[LOG] Update
[LOG] Update
[LOG] Update
[LOG] End
That is all folks!

The idea is that your base class implements a certain contract for its implementors to "follow". An example that I gave is limited to certain ways of providing implementation, but you can always provide a method to "register" the behavior of the methods.

I think that this can be improved further, depending on the needs and what needs to be achieved, but hope this gives an idea of what I was thinking as well.

Edit:

I just saw that idea about events was posted, so this is somewhat of a simplified implementation of it.

Don't define your Update method as virtual in the base class, then have a new protected virtual (or abstract) method called PhaseUpdate() which you call from within Update. Thereby forcing all calls to update through the base method.

using System;

public abstract class Phase {
    public void Start() {
        Console.Write("[LOG] Start");
        PhaseStart();

    }

    /// <summary>
    /// Returns true when finished.
    /// </summary>
    public bool Update() {
        Console.Write("[LOG] Update");
        return PhaseUpdate();
    }

    public void End() {
        Console.Write("[LOG] End");
        PhaseEnd();
    }

protected void PhaseStart() {
        
    }

    /// <summary>
    /// Returns true when finished.
    /// </summary>
    protected virtual bool PhaseUpdate() {
       
        return true;
    }

    protected virtual void PhaseEnd() {
        
    }

}
public class PhaseMenu : Phase {
    private byte _counter;

    protected override void PhaseStart() {
        _counter = 0;
    }
    
    protected override bool PhaseUpdate() {
        _counter++;
        if ( _counter > 100 ) {
            return true;
        }
        return false;
    }

    protected override void PhaseEnd() {

    }
}

Related