My
previous post suggested that it is possible to apply the same logic to elements that are disparate, that is do not have a common base class or interface that is relevant to the operation. This post describes a method of doing this using anonymous methods. I will use a simple example of summing values, but the principal is applicable generally, and is not specific to looping.
Take for instance the following code:
List<int> integerList = new List<int>();
for (int initialValue = 1; initialValue <= 5; initialValue++)
{
integerList.Add(initialValue);
}
int sum = 0;
foreach (int value in integerList)
{
sum += value;
}
Console.WriteLine(sum);
This is a simple loop that sums upĀ We can extract a method from this code like so:
private static int CalculateSum(IEnumerable<int> integerList)
{
int sum = 0;
foreach (int value in integerList)
{
sum += value;
}
return sum;
}
The problem with this method is that it only applies to types that are IEnumerable<int>. It can't be applied to a type such as:
public class TestItem
{
private int _firstValue;
private int _secondValue;
public TestItem()
{}
public TestItem(int firstValue, int secondValue)
{
_firstValue = firstValue;
_secondValue = secondValue;
}
public int FirstValue
{
get { return _firstValue; }
set { _firstValue = value; }
}
public int SecondValue
{
get { return _secondValue; }
set { _secondValue = value; }
}
}
For a simple sum operation this isn't really a problem, but for a more complicted calculation having to duplicate the logic for each possible type is a serious maintainance issue. Through the use of anonymous methods we can adapt our system to remove this redundancy.
I'll start by providing a mechanism that can sum any value on (or that can be derived from) TestItem, then extend this solution to support any type.
Firstly, we define a delegate:
public delegate int GetValue(TestItem item);
This delegate returns an integer value and takes a TestItem as a parameter. This allows us to define methods that will return whatever value we need from the TestItem, be it one of the properties or some calculated value. We could even ignore the TestItem parameter if we chose, but we will choose not to here.
The modified CalculateSum method now looks like this:
private static int CalculateSum(IEnumerable<TestItem> testItems,
GetValue valueGetter)
{
int sum = 0;
foreach (TestItem value in testItems)
{
sum += valueGetter(value);
}
return sum;
}
Now rather than using the item from the list directly this method will use the delegate it's passed. The following demonstrates the use of this method:
List<TestItem> testItems = new List<TestItem>();
for (int initialValue = 1; initialValue <= 5; initialValue++)
{
testItems.Add(new TestItem(initialValue, initialValue + 1));
}
int firstSum = CalculateSum(testItems, delegate(TestItem item) { return item.FirstValue; });
int secondSum = CalculateSum(testItems, delegate(TestItem item) { return item.SecondValue; });
int thirdSum = CalculateSum(testItems, delegate(TestItem item) { return item.FirstValue + item.SecondValue; });
Console.WriteLine(firstSum);
Console.WriteLine(secondSum);
Console.WriteLine(thirdSum);
It is here that the anonymous method comes in. This example uses three anonymous methods to return different values. The first two return their respective parameters, the thrid returns a calculated value that is the sum of these parameters. From the point of vie of CalculateSum these anonymous methods are all equivalent. You could also pass a delegate to a non-anonymous or dynamically generated method, CalculateSum does not care about such implementation details.
This is all very nice and perfectly adequate for most circumstances. However it still has the limitation that it can only work with one type. So let's genericise it. The delegate becomes:
public delegate int GetValue<T>(T item);
And the CalculateSum method becomes:
private static int CalculateSum<T>(IEnumerable<T> testItems,
GetValue<T> valueGetter)
{
int sum = 0;
foreach (T value in testItems)
{
sum += valueGetter(value);
}
return sum;
}
Thanks to the wonders of the C# compiler, no other code need be changed as the compiler can automatically work out the type of T from the usage (which is nice).
Making the return type of the delegate generic is somewhat more difficult and not always possible. The example above doesn't allow it as there is no constraint that indicates that the return type supports the += operation. Still, with suitable constraints it would be possible to do this for some operations.