cubrman
cubrman

Reputation: 946

c# call a method whenever another method ends?

Although this question might sound stupid at first glance, please hear me out.

c#'s get{} and set{} methods are increadibly usefull in situations when you do not know how you porgramming goals will evolve while you build your code. I enjoyed their freedom many a time and now I wonder if there is something similar for methods but in a bit different light.

As I am working in gamedev, it is a very common practice to extend/update/improve existing code day-in day-out. Therefore, one of the patterns I taught myself to follow is to never use "return" statement more than once in the most of my methods.

The reason why I do this is to always be able to write something at the bottom of the method and be sure that the line I have written is always called 100% of the time once my method ENDS.

Here is an example:

    public void Update()
    {
        UpdateMovement();


        if (IsIncapacitated)
            return;

        if (IsInventoryOpened)
        {
            UpdateInventory();
            return;
        }

        if (Input.HasAction(Actions.Fire))
        {
            Fire();
            return;
        }
        else if (Input.HasAction(Actions.Move))
        {
            Move(Input.Axis);
            return;
        }


    }

Now imagine that this method is called dozens of times in many places across the entirety of your project. And then the next day you decide that you need to call UpdatePhysics() method at the very end of your Update() method. In this case there are only 4 returns, it could be much worse in reality.

Then imagine that such decesions happen several times a day every day. Bad planning you may say? I might agree with you, but I do think that freedom of development is essential in modern coding. I don't think you should kill yourself trying to anticipate every turn your project might take before you start writing code.

One way to insure that problems like the one I described above never happen is to rewrite the method as follows:

    public void Update()
    {
        UpdateMovement();


        if (!IsIncapacitated)
        {
            if (IsInventoryOpened)
            {
                UpdateInventory();
            }
            else
            {
                if (Input.HasAction(Actions.Fire))
                {
                    Fire();
                }
                else if (Input.HasAction(Actions.Move))
                {
                    Move(Input.Axis);
                }
            }
        }

    }

In this case you can always add a line at the bottom and be sure it will always get called nomatter what.

So I wanted to ask if there is another approach that could allow for placing "return"-s wherever you wish while still being able to add extra code easily at the bottom of the method any time. Maybe there is any form of syntax in c# that does it for you? Or maybe there is a better coding practice that eliminates such problem?

UPDATE: As I started receiving answers, I realized that I need to clarify things a bit.

  1. 'try/catch/finally' is an overkill - I will never use them. They have severe performance penalty on catch(), they screw up 'Edit and continue' feature in Visual Studio and they just look ugly.

  2. Idealy I need to be able to access local variables in the Update() method from any code I decide to add at the end of the method,

  3. When I wrote the question, I already had an answer - nesting. My second code sample has no returns and, therefore I can add code to the very bottom of the method and it will work 100% of the time, while I will be able to use local variables. Nesting is bad though, and that is why I am here searching for a BETTER solution.

UPDATE 2: I was actually mistaken about try/catch because I did not know that you can skip catch alongside it's performance penalties and only have finally. However, this solution is still worse than the nesting solution provided in the question, because in your newly added finally block you no longer can use return statements. So basically you can do whatever you want when you write the method the first time, but once you extend it - you are back to nesting.

Upvotes: 7

Views: 12784

Answers (8)

Igor B.
Igor B.

Reputation: 2229

With the modern c# 8 syntax you may introduce some disposable 'ScopeFinalizer' object or name whatever you want:

public class ScopeFinalizer : IDisposable
{
    private Action delayedFinalization;
    public ScopeFinalizer(Action delayedFinalization)
    {
        this.delayedFinalization = delayedFinalization ?? throw new ArgumentNullException(nameof(delayedFinalization));
    }

    public void Dispose()
    {
        delayedFinalization();
    }
}

//usage example
public async Task<bool> DoWorkAsyncShowingProgress()
{
    ShowActivityIndicator();
    using var _ = new ScopeFinalizer(() =>
    {
        // --> Do work you need at enclosure scope leaving <--
        HideActivityIndicator();
    });
    var result = await DoWorkAsync();
    HandleResult(result);
    //etc ...
    return true;
}

Useful link: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#using-declarations

Upvotes: 1

mark_h
mark_h

Reputation: 5477

Using a try/finally block should work;

    public void Update()
    {
        try
        {
            UpdateMovement();


            if (IsIncapacitated)
                return;

            if (IsInventoryOpened)
            {
                UpdateInventory();
                return;
            }

            if (Input.HasAction(Actions.Fire))
            {
                Fire();
                return;
            }
            else if (Input.HasAction(Actions.Move))
            {
                Move(Input.Axis);
                return;
            }
        }
        finally
        {
            //this will run, no matter what the return value
        }
    }

The performance costs of using try/finally (not try/catch!) are minimal

You cannot use return in the finally block;

If you were able to return a different value from the Finally block, this value would always be returned, whatever the outcome of the instructions above. It just wouldn't make sense..

Upvotes: 6

Jakey113G
Jakey113G

Reputation: 168

After seeing the other solutions I can't think of a truly dynamic solution that has only the functions you want to call in the update loop.

Here are some ideas though I doubt any of them are better than making a good design. Joe C has the correct idea of how you should structure this kind of thing.


You could make a container of actions that need to be performed each update loop. Remove and add specific actions depending on the changes to circumstances. Such as a IsNowIncapacitated event that remove the Handling action from the list. Though I have little experience with actions, I believe you can set up delegates that the actions point to. Not sure what the cost to performance is.

A temporary thing you could do so you can keep adding logic is have your return statements return a void function with some constant logic you want performed, though all it really will do is separate your update code between two methods. It is not very neat or as efficient as structuring your code appropriately like in Joe C's example.

public void PostUpdate()
{
    //stuff that always happens
    PhysicsUpdate();
}

 public void Update()
{
    UpdateMovement();

    if (IsIncapacitated)
        return PostUpdate();

    if (IsInventoryOpened)
    {
        UpdateInventory();
        return PostUpdate();
    }
}

Upvotes: 0

AlanT
AlanT

Reputation: 3663

A problem with the current approach is that it requires changes to the Update() method whenever we want to add a new action.

Another approach is to remove the hard-coding of the update actions and configure the class with a set of update actions.

From the code given here we have

  1. Actions that always happen (e.g. UpdateMovement)
  2. Actions that happen if a test is passed (e.g. UpdateInventory)
  3. Actions that cause a return if they are executed (e.g. Fire())

We can encapsulate these in an interface

public interface IUpdateAction
{
    bool ShouldUpdate();

    // return true if we want this to be the last action to be executed
    bool Update();
}

and wrap various actions and decisions in the class using

public class DelegateUpdateAction : IUpdateAction
{
    private Func<bool> _updateAction;
    private Func<bool> _shouldUpdateCheck;

    public DelegateUpdateAction(Action action, bool isLastAction = false, Func<bool> shouldUpdateCheck = null)
        : this(() =>
        {
            action();
            return isLastAction;
        },
        shouldUpdateCheck)
    { }

    public DelegateUpdateAction(Func<bool> updateAction, Func<bool> shouldUpdateCheck = null)
    {
        if(updateAction == null)
        {
            throw new ArgumentNullException("updateAction");
        }
        _updateAction = updateAction;
        _shouldUpdateCheck = shouldUpdateCheck ?? (() => true); 
    }

    public bool ShouldUpdate()
    {
        return _shouldUpdateCheck();
    }

    public bool Update()
    {
        return _updateAction();
    }
}

To replicate the example we could use

public class Actor
{
    private IEnumerable<IUpdateAction> _updateActions;

    public Actor(){
        _updateActions = new List<IUpdateAction>{
            new DelegateUpdateAction((Action)UpdateMovement),
            new DelegateUpdateAction((()=>{ }), true, () => IsIncapacitated),
            new DelegateUpdateAction((Action)UpdateInventory, true, () => IsInventoryOpened),
            new DelegateUpdateAction((Action)Fire, true, () => Input.HasAction(Actions.Fire)),
            new DelegateUpdateAction(() => Move(Input.Axis), true, () => Input.HasAction(Actions.Move))
        };
    }

    private Input Input { get; set; }

    public void Update()
    {
        foreach(var action in _updateActions)
        {
            if (action.ShouldUpdate())
            {
                if (action.Update())
                    break;
            }
        }
    }

    #region Actions

    private bool IsIncapacitated { get; set; }
    private bool IsInventoryOpened { get; set; }

    private void UpdateMovement()
    {
    }

    private void UpdateInventory()
    {
    }

    private void Fire()
    {
    }

    private void Move(string axis)
    {
    }

    #endregion
}

The actions are executed in the order in which they are registered, so this gives us the ability to inject a new action into the execution sequence at any point.

  • UpdateMovement() always happens and doesn't return
  • IsIncapacitated() is a test with a null action. It returns if executed so we get our 'do-nothing-else-if-incapacitated' behaviour
  • UpdatedInventory() occurs if the inventory is open and then returns
  • Each of the HasAction checks return if executed.

Note If I have read the question better before writing the code I would have reversed the defaults as most actions seem to be 'return if executed'.

If we need to add 'UpdatePhysics()', we add a method to the class and add an entry in the appropriate place in the list of update actions. No changes to the Update method.

If we have derived classes with different actions we can add the facility to add (or remove) actions in the derived classes and either inherit and modify the default actions or replace them with a different set.

Upvotes: 0

Maximilian Ast
Maximilian Ast

Reputation: 3499

You can use try-catch-finally (C#-Reference) without a catch block.

try
{
    //your business logic here
}
finally
{
    //will be called anytime if you leave the try block
    // i.e. if you use a return or a throw statement in the try block
}

Upvotes: 2

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186688

I suggest wrapping the code into try..finally block:

  public void Update() {
    try {
      ...
      // you can return  
      if (someCondition)
        return;
      ...
      // throw exceptions
      if (someOtherCondition)
        throw... 
      ...
    }
    finally {
      // However, finally will be called rain or shine
    }
  } 

Upvotes: 3

Timothy Stepanski
Timothy Stepanski

Reputation: 1196

Don't use the returns as it makes your code smelly.

public void Update()
{
    UpdateMovement();


    if (IsIncapacitated){
        return;
    }
    if (IsInventoryOpened)
    {
        UpdateInventory();
    }
    else if (Input.HasAction(Actions.Fire))
    {
        Fire();
    }
    else if (Input.HasAction(Actions.Move))
    {
        Move(Input.Axis);
    }
}

Also, your second solution has too much nesting, also confusing and smelly.

Upvotes: 0

Joe C
Joe C

Reputation: 3993

One simple suggestion is to wrap your function. For example:

public void UpdateCall()
{
   Update();
   AfterUpdate code goes here.
}

Upvotes: 5

Related Questions