When trying to use delegates in C# to solve a problem in a functional way, I've come across a pitfall that I want to share resp. for which I would like to hear your suggestions.
Background
I want to fill a grid from a list of objects where the values for single columns are get using delegates (idea borrowed from Philip Pipers ObjectListView control).
Additionally I want to automatically insert columns containing the (numerical) difference between two values.
So my objects having properties FirstValue
, SecondValue
and ThirdValue
I want to have columns with FirstValue
, (SecondValue-FirstValue)
, SecondValue
, (ThirdValue-SecondValue)
, ThirdValue
.
I already have adapted an existing grid control to use delegates on an object list, this part works fine.
First attempt
First, I tried something like:
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));
}
};
Problem
In a functional language, calculating the difference in this way would work fine, since the new delegate (constructed inside AddMyColumn
) would use the value of lastGetter
at the time of construction. But in C#, the new delegate uses a reference to lastGetter
, so when executed, it uses the actual value at the time of execution. So the difference will always be built against the last column (i.e. obj.ThirdValue
).
Solution
One solution I've found for myself is
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);
}));
}
// ...
}
Note that
if (lastGetter != null) {
DelegateGrid.ValueGetter newLastGetter =
delegate(MyObject obj){return lastGetter(obj); };
wouldn't have solved the problem.
Question
Already having found a solution, this part is a bit pro forma, but
- Does anyone have a suggestion for a better solution
- I'm using C#2.0 and have only a theoretical knowledge of lambda expressions in C#3.0: Would they allow for a cleaner solution (and thus deserve their name...)?