2009-03-30 3 views
4

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

фон

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

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

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

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

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

Во-первых, я пытался что-то вроде:

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) будет использовать значение of 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: разрешат ли они более чистое решение (и, следовательно, заслуживают их имени ...)?

ответ

7

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

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

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

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

становится:

obj => getter(obj)-newLastGetter(obj) 
Смежные вопросы