C # Lambdas: как * не * отложить "разыменование"? - PullRequest
9 голосов
/ 15 февраля 2010

Я пытаюсь реализовать функцию отмены с делегатами C #. По сути, у меня есть UndoStack, который поддерживает список делегатов, реализующих каждую операцию отмены. Когда пользователь выбирает Edit: Undo, этот стек выскакивает из первого делегата и запускает его. Каждая операция отвечает за передачу соответствующего делегата отмены в стек. Итак, предположим, у меня есть метод с именем «SetCashOnHand». Это выглядит так:

public void SetCashOnHand(int x) {
    int coh = this.cashOnHand;
    undo u = () => this.setCashOnHand(coh);
    this.cashOnHand = x;
    undoStack.Push(u);
}

Таким образом, этот метод создает делегат отмены, выполняет операцию и (при условии, что он успешен) помещает делегат отмены в UndoStack. (UndoStack достаточно умен, чтобы при вызове undoStack.Push в контексте отмены делегат отправлялся в стек повторного выполнения.)

Моя проблема в том, что немного раздражает кеширование this.cashOnHand в переменную coh. Я хотел бы просто написать это:

undo u = () => this.setCashOnHand(this.cashOnHand);

Но, конечно, это не даст настоящее значение cashOnHand; он откладывает поиск значения до вызова делегата, поэтому код ничего не делает. Есть ли способ, которым я могу «разыменовать» cashOnHand при создании делегата, кроме вставки значения в локальную переменную, например, coh?

Мне не очень интересно слышать о лучших способах отмены. Пожалуйста, подумайте об этом как об общем вопросе о делегатах, а «Отменить» - просто пример, чтобы сделать вопрос более ясным.

Ответы [ 5 ]

6 голосов
/ 15 февраля 2010

В общем, не существует идеального решения для этого, кроме того, что вы уже делаете.

Однако в вашем конкретном случае вы можете сделать currier , например:

static Action Curry(Action<T> method, T param) { return () => method(param); }

Вы можете использовать это так:

Curry(setCashOnHand, this.cashOnHand);

Если ваш метод принимает другие параметры, вы можете использовать его следующим образом:

Curry(cashOnHand => setCashOnHand(3, cashOnHand), this.cashOnHand);
2 голосов
/ 15 февраля 2010

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

Это ничем не отличается от того, что вы написали бы:

public void SetCashOnHand(int x) {
    this.cashOnHand = x;
    undoStack.Push(UndoSetCashOnHand);
}

private void UndoSetCashOnHand()
{
    this.setCashOnHand(this.cashOnHand);
}

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

Люди обычно обходят это, используя параметризованную функцию и сохраняя значение, которое передается функции как часть стека. Однако, если вы хотите использовать параметр - less , вам придется захватить его в локальном режиме.

0 голосов
/ 15 февраля 2010

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

Например:

public class MyType
{

  private UndoStack undoStack {get;set;}

  public void SetCashOnHand(int x) {
    int coh = this.cashOnHand;
    undo u = () => this.setCashOnHand(coh);
    this.cashOnHand = x;
    undoStack.Push(u);
   }
}

тогда не имеет значения, на что ссылается UndoStack. Если вы вводите UndoStack и ссылки на этот экземпляр хранятся в другом месте, это будет иметь значение.

Если второе верно, я хотел бы подумать о рефакторинге, а не пытаться придумать какую-то слабую ссылку или другое чиканье, чтобы предотвратить утечку экземпляров через стек отмены.

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

0 голосов
/ 15 февраля 2010

Делегат выполняется в будущем. Я думаю, что временная переменная, вероятно, является лучшим способом для обработки «запускайте код сейчас, а не в будущем». Тем не менее, я думаю, вы могли бы написать функцию Bind (), которая привязывает текущее значение к делегату.

Action Bind<T>(Func<T> f, T value) { 
    return (Action)(() => f(value));
}
undoStack.Push(Bind(this.setCashOnHand, this.cashOnHand));
0 голосов
/ 15 февраля 2010

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

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