Автоматический вызов метода после блока кода - PullRequest
1 голос
/ 16 января 2012

Я добавляю понятие действий, которые можно повторить через заданный интервал времени в моей игре.
У меня есть класс, который управляет выполнением заданного действия.
Вызывающие пользователи запрашивают, могут ли они выполнитьвызывая CanDoAction, затем, если это так, выполните действие и запишите, что они выполнили действие с MarkActionDone.

if (WorldManager.CanDoAction(playerControlComponent.CreateBulletActionId))
{
    // Do the action

    WorldManager.MarkActionDone(playerControlComponent.CreateBulletActionId);
}

Очевидно, что это может привести к ошибкам, так как вы можете забыть вызватьMarkActionDone, или, возможно, вы могли бы забыть позвонить CanDoAction для проверки.

В идеале я хочу сохранить подобный интерфейс, не нужно пропускать Action или что-то подобное, поскольку яработает на Xbox и предпочел бы не передавать действия и вызывать их.Тем более, что должно быть много замыканий, поскольку действия обычно зависят от окружающего кода.

Я думал о том, как (ab) использовать интерфейс IDisposeable, поскольку это обеспечит MarkActionDone можно вызвать в конце, однако я не думаю, что смогу пропустить блок использования, если CanDoAction будет ложным.

Есть идеи?

Ответы [ 2 ]

4 голосов
/ 16 января 2012

Мой предпочтительный подход состоял бы в том, чтобы сохранить эту логику как деталь реализации WorldManager (поскольку она определяет правила о том, можно ли выполнить действие), используя шаблон делегата:

public class WorldManager
{
  public bool TryDoAction(ActionId actionId, Action action)
  {
    if (!this.CanDoAction(actionId)) return false;
    try
    {
      action();
      return true;
    }
    finally
    {
      this.MarkActionDone(actionId);
    }
  }

  private bool CanDoAction(ActionId actionId) { ... }
  private void MarkActionDone(ActionId actionId) { ... }
}

Похоже, это лучше всего подходит для принципалов SOLID, поскольку позволяет избежать того, чтобы любой другой класс «знал» о деталях реализации «CanDoAction», «MarkActionDone» WorldManager.

Обновление

Использование инфраструктуры AOP, такой как PostSharp , может быть хорошим выбором, чтобы гарантировать, что этот аспект добавлен ко всем необходимым кодовым блокам чистым способом.

2 голосов
/ 16 января 2012

Если вы хотите минимизировать давление ГХ, я бы предложил использовать интерфейсы, а не делегатов. Если вы используете IDisposable, вы не можете избежать вызова Dispose, но вы можете заставить реализацию IDisposable использовать флаг, чтобы указать, что метод Dispose ничего не должен делать. Помимо того факта, что делегаты имеют некоторую встроенную языковую поддержку, на самом деле они ничего не могут сделать, что интерфейсы не могут, но интерфейсы предлагают два преимущества перед делегатами:

  1. Использование делегата, который связан с некоторыми данными, обычно требует создания объекта кучи для данных и второго для самого делегата. Интерфейсы не требуют второго экземпляра кучи.
  2. В обстоятельствах, когда можно использовать универсальные типы, которые ограничены интерфейсом, вместо непосредственного использования типов интерфейса, можно избежать создания каких-либо экземпляров кучи, как объяснено ниже (поскольку обратное форматирование не работает в элементы списка). Структура, которая объединяет делегат статического метода вместе с данными, которые будут использоваться этим методом, может вести себя как делегат, не требуя выделения кучи.

Одно предостережение со вторым подходом: хотя избегать давления GC - хорошая вещь, второй подход может привести к созданию очень большого количества типов во время выполнения. Количество создаваемых типов в большинстве случаев будет ограничено, но существуют обстоятельства, когда оно может увеличиваться без ограничений. Я не уверен, есть ли какой-нибудь удобный способ определить полный набор типов, которые могут быть созданы программой в случаях, когда статического анализа будет достаточно (в общем случае, когда статического анализа недостаточно, определяя, является ли какой-либо производимый тип во время выполнения был бы эквивалентен проблеме остановки, но так же, как многие программы на практике могут быть определены статически, чтобы всегда останавливаться или никогда не останавливаться, я ожидаю, что на практике часто можно идентифицировать закрытый набор типов, которые программа может производить во время выполнения).

Редактировать

Форматирование в пункте № 2 выше было испорчено. Вот объяснение, приведенное в порядок.

Определите тип ConditionalCleaner<T> : IDisposable, который содержит экземпляр T и Action<T> (оба предоставляются в конструкторе - возможно, с Action<T> в качестве первого параметра). В методе IDisposable.Dispose(), если Action<T> не равен NULL, вызовите его для T. В методе SkipDispose() обнулите Action<T>. Для удобства вы можете захотеть также определить ConditionalCleaner<T,U>: IDisposable аналогичным образом (возможно, версии с тремя и четырьмя аргументами), и вы можете определить статический класс ConditionalCleaner с помощью универсальных методов Create<T>, Create<T,U> и т. Д. ( так, например, можно сказать, например, using (var cc = ConditionalCleaner.Create(Console.WriteLine, "ABCDEF") {...} или ConditionalCleaner.Create((x) => {Console.WriteLine(x);}, "ABCDEF"), чтобы указанное действие выполнялось при выходе из блока using. Самое большое требование, если используется выражение Lambda, - убедиться, что лямбда-выражение не закрывается ни по каким локальным переменным или параметрам из вызывающей функции; все, что вызывающая функция хочет передать в лямбда-выражение, должно быть его явным параметром. В противном случае система определит объект класса для хранения любых закрытых переменных, а также новый делегат, указывающий на него.

...