Составление делегатов (функциональная ловушка) - PullRequest
4 голосов
/ 30 марта 2009

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

Фон

Я хочу заполнить сетку из списка объектов, где значения для отдельных столбцов получают с использованием делегатов (идея заимствована из элемент управления Philip Pipers ObjectListView ).

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

Итак, мои объекты, имеющие свойства FirstValue, SecondValue и ThirdValue, я хочу иметь столбцы с FirstValue, (SecondValue-FirstValue), SecondValue, (ThirdValue-SecondValue), ThirdValue.

Я уже адаптировал существующий элемент управления grid для использования делегатов в списке объектов, эта часть отлично работает.

Первая попытка

Сначала я попробовал что-то вроде:

class MyGridClass : DelegateGrid
{
  DelegateGrid.ValueGetter lastGetter;

  public MyGridClass() {
    AddMyColumn(delegate(MyObj obj) { return obj.FirstValue; });
    AddMyColumn(delegate(MyObj obj) { return obj.SecondValue; });
    AddMyColumn(delegate(MyObj obj) { return obj.ThirdValue; });
  }

  private void AddMyColumn(DelegateGrid.ValueGetter getter) {
    if (lastGetter != null)
      base.AddColumn(new DelegateColumn(delegate(MyObj obj) { 
        return getter(obj)-lastGetter(obj); 
      }));
    base.AddColumn(new DelegateColumn(getter));
  }
};

Проблема

На функциональном языке вычисление разницы таким способом будет работать нормально, поскольку новый делегат (созданный внутри AddMyColumn) будет использовать значение из lastGetter во время построения. Но в C # новый делегат использует от ссылки до lastGetter, поэтому при выполнении он использует фактическое значение во время выполнения. Таким образом, разница всегда будет построена по последнему столбцу (т. Е. obj.ThirdValue).

Решение

Одно решение, которое я нашел для себя, это

public AddMyColumn(DelegateGrid.ValueGetter getter) {
  if (lastGetter != null) {
    DelegateGrid.ValueGetter newLastGetter = 
      new DelegateGrid.ValueGetter(lastGetter);
    base.AddColumn(new DelegateColumn(delegate(MyObj obj) { 
     return getter(obj)-newLastGetter(obj); 
    }));
  }
  // ...
}

Обратите внимание, что

if (lastGetter != null) {
  DelegateGrid.ValueGetter newLastGetter = 
    delegate(MyObject obj){return lastGetter(obj); };

не решил бы проблему.

Вопрос

Уже найдя решение, эта часть немного проформа, но

  • У кого-нибудь есть предложения по лучшему решению
  • Я использую C # 2.0 и имею только теоретические знания о лямбда-выражениях в C # 3.0: позволят ли они найти более чистое решение (и, следовательно, заслуживают своего имени ...)?

Ответы [ 2 ]

7 голосов
/ 30 марта 2009

Проблема только в том, что переменная фиксируется, а не значение. Вот решение, которое является почти таким же, но немного более простым:

public AddMyColumn(DelegateGrid.ValueGetter getter) {
  if (lastGetter != null) {
    DelegateGrid.ValueGetter newLastGetter = lastGetter;
    base.AddColumn(new DelegateColumn(delegate(MyObj obj) { 
     return getter(obj)-newLastGetter(obj); 
    }));
  }
  // ...
}

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

Это на самом деле не специфичная для делегата проблема с точки зрения захвата значения - это общая проблема для анонимных методов и лямбда-выражений в целом. Типичный пример:

List<Action> actions = new List<Action>();
for (int i=0; i < 10; i++)
{
    actions.Add(() => Console.WriteLine(i));
}
foreach (Action action in actions)
{
    action();
}

Это печатает "10" 10 раз. Чтобы напечатать 0-9, вам снова нужно изменить область захваченной переменной:

List<Action> actions = new List<Action>();
for (int i=0; i < 10; i++)
{
    int copy = i;
    actions.Add(() => Console.WriteLine(copy));
}
foreach (Action action in actions)
{
    action();
}
0 голосов
/ 30 марта 2009

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

delegate(MyObj obj) { 
    return getter(obj)-newLastGetter(obj); 
}

становится:

obj => getter(obj)-newLastGetter(obj)
...