2016-06-14 2 views
2

Хотя этот вопрос может показаться глупым на первый взгляд, пожалуйста, выслушайте меня.C# вызывать метод всякий раз, когда заканчивается другой метод?

Методы C# 'get {} и set {} являются чрезвычайно полезными в ситуациях, когда вы не знаете, как ваши задачи программирования будут развиваться при создании вашего кода. Я наслаждался их свободой много раз, и теперь мне интересно, есть ли что-то подобное для методов, но в немного другом свете.

Поскольку я работаю в gamedev, очень распространенной практикой является расширение/обновление/улучшение существующего дневного дневного кода. Поэтому один из шаблонов, которым я научился следовать, заключается в том, чтобы никогда не использовать «возврат» более одного раза в большинстве моих методов.

Причина, по которой я делаю это, - всегда иметь возможность написать что-то в нижней части метода и быть уверенным, что строка, которую я написал, всегда называется 100% времени, как только мой метод ENDS.

Вот пример:

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


    } 

Теперь представьте себе, что этот метод называют десятки раз во многих местах по всей полноте вашего проекта. И затем на следующий день вы решите, что вам нужно вызвать метод UpdatePhysics() в самом конце вашего метода Update(). В этом случае есть только 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 какой.

Так что я хотел спросить, есть ли другой подход, который может позволить разместить «return» -s, где бы вы ни пожелали, все еще будучи в состоянии добавить дополнительный код в нижней части метода в любое время. Может быть, есть какая-то форма синтаксиса в C#, которая делает это для вас? Или, может быть, существует более совершенная практика кодирования, которая устраняет такую ​​проблему?

ОБНОВЛЕНИЕ: Когда я начал получать ответы, я понял, что мне нужно немного прояснить ситуацию.

  1. «try/catch/finally» - это излишество - я их никогда не буду использовать. У них есть серьезная штрафная сила при уловах(), они прикручивают функцию «Редактировать и продолжить» в Visual Studio, и они просто выглядят уродливыми.

  2. Idealy Мне нужно, чтобы иметь возможность получить доступ к локальным переменным в методе Update() из любого кода, я решаю добавить в конце методы,

  3. Когда я писал этот вопрос, я уже имел ответ - вложенность. У моего второго примера кода нет возвратов, и поэтому я могу добавить код в самую нижнюю часть метода, и он будет работать в 100% случаев, тогда как я смогу использовать локальные переменные. Вложенность плохая, хотя, и именно поэтому я здесь ищу решение BETTER.

UPDATE 2: Я был на самом деле ошибся try/catch, потому что я не знаю, что вы можете пропустить catch вместе это штрафы производительности и только finally. Тем не менее, это решение по-прежнему хуже, чем решение для размещения в этом вопросе, потому что в недавно добавленном блоке finally вы больше не можете использовать операторы return. Таким образом, в основном вы можете делать все, что захотите, когда пишете метод в первый раз, но как только вы его продлеваете - вы вернетесь в гнездование.

+4

попытка/наконец или (если применимо) Задача продолжения ... –

+0

Я предполагаю, что вы ищете событие «onReturn» или «beforeReturn», чтобы подписаться? Я бы пошел с Try/Catch/Наконец, хотя. В зависимости от вашей обработки ошибок – DarthJam

+0

«finally» не работает без «try». С другой стороны, покрытие всего метода в 'try' может привести к невероятно странному поведению, поскольку ошибки будут пропущены. Более того, попытка не может использоваться в сборках релизов из-за причин производительности (я знаю, что штраф происходит только тогда, когда ошибка поймана), за исключением особых случаев. Не понял, что вы делаете с помощью «Продолжения задачи», хотя ... – cubrman

ответ

5

Одно простое предложение - обернуть свою функцию. Например:

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

Действительно, вызов 'UpdateMovement();' должен быть выведен на это, а метод 'Update' называется чем-то более подходящим. Затем вы получаете «public void Update() {UpdateMovement(); UpdateAction(); UpdatePhysics();}' – Lithium

+0

Код был просто примером - никогда не использовался нигде. – cubrman

+0

Joe. Хотя ответ выглядит блестящим и должен быть exaccty, который я ищу, у него есть существенный недостаток: что, если мне нужно получить доступ к локальным переменным из Update() в AfterUpdate? Я могу сделать это в своем решении, я не могу сделать это в твоем. – cubrman

-2

Не используйте возвраты, так как это делает ваш код вонючим.

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

Кроме того, у вашего второго решения слишком много гнездящихся, также запутывающих и вонючих.

+0

В ответ вы оба: используйте return AND have nesting, в чем смысл? Вложенность плохая, поэтому я здесь задаю свой вопрос. – cubrman

+0

Я не использовал избыточные данные, также, я не вложил никаких утверждений if. –

+0

Да, мое плохое, нет гнездования, но я предпочел бы жить с гнездом, чем использование возвращается где-нибудь в моем методе (но в самом конце, если необходимо). – cubrman

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

wow, не знал, после многих лет программирования, что, наконец, даже вызвано после заявления о возврате. Благодарю. –

+0

Извините, но это всего лишь перебор - я обсуждал это в комментариях к исходному вопросу. – cubrman

2

Вы можете использовать try-catch-finally (C#-Reference) без catch блока.

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

См. Комментарии к исходному вопросу. – cubrman

+0

@cubrman, есть разница между 'Try/Catch' и' Try/FInally'. Да 'Try/Catch'-Blocks имеют недостатки производительности, но если вы используете их без блокировки, минимальные затраты на производительность, так как @mark_h разместил ссылку на сообщение об этом в своем [ответе] (http://stackoverflow.com/a/37811861/1466583) –

+0

Maximilian, это было мое плохое - я понятия не имел, что вы можете пропустить «поймать» вместе с его стоимостью исполнения. Однако, как только я пробовал этот подход на практике, я осознал значительный drowback: вы больше не можете использовать «return-s» в блоке «finally», и это ограничение фактически отрицает все преимущества, которые приносит решение, поскольку природа вопроса исчезает бесплатно , непрерывная итерация по одному методу. – cubrman

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;

Если бы вы были в состоянии вернуть другое значение из блока Наконец, это значение всегда будет возвращено, независимо от результата из инструкции выше. Это просто не имеет смысла.

+0

Надеюсь, что люди, которые повышают это, понимают стороны производительности try/catch, а также их взаимодействие с Visual Studio. Конечно, это решение, но не решение, которое будет работать для неопределенных проектов, где вы нацелены на самую быструю разработку, используя все современные инструменты для этого. – cubrman

+1

Привет, спасибо за ссылку, mark_h. Это действительно полезно. Я не знал, что «поймать» можно пропустить. Однако, попробовав ваше решение, я нашел один важный недостаток: код в блоке «finally» не может иметь собственных возвратов. В основном это означает, что вы можете быть настолько свободны, как хотите разместить возврат в исходном методе, но как только вы захотите его продлить, вы сразу же потеряете свободу, которую вы когда-то имели :). Я бы назвал это ситуационным решением, и я настоятельно рекомендую вам добавить эту информацию в свой ответ, поскольку я думаю, что это очень важно, учитывая характер вопроса. – cubrman

+1

Я отредактировал свой ответ, как было предложено. –

0

Проблема с нынешним подходом заключается в том, что он требует изменений в методе Update() всякий раз, когда мы хотим добавить новое действие.

Другой подход заключается в удалении жесткого кодирования действий по обновлению и настройке класса с помощью набора действий по обновлению.

Из кода приведенного здесь мы имеем

  1. Действия, которые всегда происходят (например, UpdateMovement)
  2. действия, которые происходят, если тест пройден (например, UpdateInventory)
  3. действия, которые вызывают возвращение, если они выполняются (например,Огонь())

Мы можем инкапсулировать их в интерфейсе

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 
} 

Действия выполняются в der, в котором они зарегистрированы, поэтому это дает нам возможность вводить новое действие в последовательность выполнения в любой точке.

  • UpdateMovement() всегда бывает и не возвращает
  • IsIncapacitated() тест с нулевым действием. Он возвращается, если выполняется таким образом мы получаем наш «делать-ничего-то еще, если-не недееспособными» поведение
  • UpdatedInventory() происходит, если инвентаризация открыта, а затем возвращает
  • Каждый из HasAction возвращения чеки если выполнено.

Примечания Если я прочитал этот вопрос лучше, прежде чем писать код, который я бы обращенный по умолчанию, как большинство действий, как представляется, «возвращения выполняется».

Если нам нужно добавить 'UpdatePhysics()', мы добавим метод в класс и добавим запись в соответствующее место в списке действий по обновлению. Никаких изменений в методе Update.

Если мы получили классы с разными действиями, мы можем добавить средство для добавления (или удаления) действий в производных классах и наследовать и изменять действия по умолчанию или заменять их другим набором.

+0

Спасибо за очень подробный ответ, но я ищу легкий подход к использованию в каждом методе, который я пишу. Я привел пример с Update(), потому что это было первое, что пришло мне в голову, я боюсь, что вы дали решение очень разной проблемы. – cubrman

0

После просмотра других решений я не могу представить себе действительно динамическое решение, которое имеет только функции, которые вы хотите вызвать в цикле обновления.

Вот некоторые идеи, хотя я сомневаюсь, что любой из них лучше, чем хороший дизайн. Джо C имеет правильное представление о том, как вы должны структурировать подобные вещи.


Вы можете создать контейнер действий, которые должны выполняться в каждом цикле обновления. Удалите и добавьте определенные действия в зависимости от изменений обстоятельств. Например, событие IsNowIncapacitated, которое удаляет действие Handling из списка. Хотя у меня мало опыта в действиях, я считаю, что вы можете настроить делегатов, на которые указывают действия. Не знаете, какова стоимость исполнения.

Временная вещь, которую вы могли бы сделать, чтобы вы могли добавлять логику, заключается в том, что операторы return возвращают функцию void с некоторой постоянной логикой, которую вы хотите выполнить, хотя все, что она действительно сделает, - это отдельный код обновления между двумя методами. Это не очень аккуратно или так же эффективно, как структурирование вашего кода, как на примере Джо С.

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

public void Update() 
{ 
    UpdateMovement(); 

    if (IsIncapacitated) 
     return PostUpdate(); 

    if (IsInventoryOpened) 
    { 
     UpdateInventory(); 
     return PostUpdate(); 
    } 
} 
Смежные вопросы