c#在另一个方法结束时调用方法?

时间:2016-06-14 12:11:03

标签: c#

虽然这个问题乍一看可能听起来很愚蠢,但请听我说。

c#的get {}和set {}方法在你不知道编译目标在构建代码时如何发展的情况下是非常有用的。我很享受他们的自由很多次,现在我想知道是否有类似的方法,但有点不同的光。

当我在gamedev工作时,日常扩展/更新/改进现有代码是一种非常普遍的做法。因此,我自学的一种模式是在我的大多数方法中不要多次使用“返回”语句。

我这样做的原因是总是能够在方法的底部写一些东西,并确保我的方法ENDS后,我所写的行总是100%被调用。

以下是一个例子:

    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;
        }


    }

现在想象一下,在整个项目的许多地方都会调用此方法几十次。然后第二天你决定需要在Update()方法的最后调用UpdatePhysics()方法。在这种情况下,只有4个回报,实际上可能会更糟。

然后想象一下,这种情况每天都会发生几次。你可能会说不好的计划?我可能同意你的意见,但我确实认为发展自由在现代编码中至关重要。在你开始编写代码之前,我认为你不应该试图预测项目可能采取的每一个转变。

确保上述问题永远不会发生的一种方法是重写方法如下:

    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);
                }
            }
        }

    }

在这种情况下,你总是可以在底部添加一行,并确保它总是被称为nomatter是什么。

所以我想询问是否有另一种方法可以允许在任何地方放置“返回”-s,同时仍然可以随时在方法的底部轻松添加额外的代码。也许c#中有任何形式的语法可以帮到你吗?或者也许有更好的编码实践可以消除这种问题?

更新:当我开始收到答案时,我意识到我需要澄清一些事情。

  1. 'try / catch / finally'是一种矫枉过正 - 我永远不会使用它们。它们对catch()有严重的性能损失,它们搞砸了Visual Studio中的“编辑并继续”功能,它们看起来很丑陋。

  2. Idealy我需要能够从我决定在方法结尾添加的任何代码中访问Update()方法中的局部变量,

  3. 当我写这个问题时,我已经有了一个答案 - 嵌套。我的第二个代码示例没有返回,因此我可以在方法的最底部添加代码,它将在100%的时间内工作,而我将能够使用局部变量。嵌套很糟糕,这就是为什么我在这里寻找更好的解决方案。

  4. 更新2 :我实际上误解了try/catch,因为我不知道您可以跳过catch以及性能处罚,只有finally。但是,此解决方案仍然比问题中提供的嵌套解决方案更糟糕,因为在新添加的finally块中,您不再可以使用return语句。所以基本上你可以在第一次编写方法时做任何你想做的事情,但是一旦你扩展它 - 你就会回到嵌套。

8 个答案:

答案 0 :(得分:6)

使用try / finally块应该可以工作;

    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
        }
    }

performance costs of using try/finally(不是尝试/捕获!)是最小的

You cannot use return in the finally block;

  

如果您能够从Finally块返回不同的值,   无论结果如何,都将返回此值   以上说明。它只是没有意义..

答案 1 :(得分:5)

一个简单的建议是包装你的功能。例如:

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

答案 2 :(得分:3)

我建议将代码包装到try..finally块:

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

答案 3 :(得分:2)

您可以在没有catch阻止的情况下使用try-catch-finally (C#-Reference)

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
}

答案 4 :(得分:1)

使用现代的c#8语法,您可以引入一些一次性的“ ScopeFinalizer”对象或随意命名:

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;
}

有用的链接: https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#using-declarations

答案 5 :(得分:0)

当前方法的一个问题是,只要我们想要添加新操作,就需要更改Update()方法。

另一种方法是删除更新操作的硬编码,并使用一组更新操作配置类。

从这里给出的代码我们有

  1. 始终发生的操作(例如UpdateMovement)
  2. 传递测试时发生的操作(例如UpdateInventory)
  3. 如果执行会导致返回的操作(例如Fire())
  4. 我们可以将它们封装在一个接口

    public interface IUpdateAction
    {
        bool ShouldUpdate();
    
        // return true if we want this to be the last action to be executed
        bool Update();
    }
    

    使用

    在课堂上包含各种动作和决定
    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();
        }
    }
    

    要复制示例,我们可以使用

    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
    }
    

    动作按照它们的注册顺序执行,因此这使我们能够在任何时候将新动作注入执行序列。

    • UpdateMovement()总是会发生并且不会返回
    • IsIncapacitated()是具有空操作的测试。它会在执行后返回,这样我们就可以获得&#39;无所事事 - 否则 - 如果 - 无行为能力 行为
    • 如果广告资源已打开,则会发生UpdatedInventory(),然后返回
    • 每次 HasAction 检查都会返回执行。

    注意如果我在编写代码之前已经更好地阅读了这个问题,那么我会撤消默认设置,因为大多数操作似乎是&#39;如果已执行则返回&#39;

    如果我们需要添加&#39; UpdatePhysics()&#39; ,我们会向该类添加一个方法,并在更新操作列表中的适当位置添加一个条目。没有对Update方法进行任何更改。

    如果我们使用不同的操作派生类,我们可以添加设施来在派生类中添加(或删除)操作,并继承和修改默认操作或用不同的集替换它们。

答案 6 :(得分:0)

在看到其他解决方案之后,我无法想到一个真正的动态解决方案,它只包含您想要在更新循环中调用的函数。

虽然我怀疑它们中的任何一个都比制作好的设计更好。乔C对你应该如何构建这类事物有正确的认识。

您可以创建一个需要在每个更新循环中执行的操作的容器。根据环境的变化删除并添加特定操作。例如IsNowIncapacitated事件,从列表中删除处理操作。虽然我对行动没什么经验,但我相信你可以设置行动指向的代表。不确定性能成本是多少。

你可以做的一件临时的事情你可以继续添加逻辑是你的return语句返回一个void函数,你需要执行一些常量逻辑,尽管它真正要做的就是在两个方法之间分离你的更新代码。它不像在Joe C的例子中那样适当地构造你的代码,也不是很整洁或高效。

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

 public void Update()
{
    UpdateMovement();

    if (IsIncapacitated)
        return PostUpdate();

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

答案 7 :(得分:-1)

不要使用退货,因为它会让你的代码发臭。

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);
    }
}

此外,你的第二个解决方案有太多的嵌套,也令人困惑和臭。