C# Coding Solutions—Understanding and Using Functors
Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio
Understanding and Using Functors
Here is a quick quiz question: Supposing you have a collection of int values and you want to find the total of the numbers, how would you find the total? Would your answer look similar to the following?
public int AddAllElements( IList< int> list) { int runningTotal = 0; foreach( int value in list) { runningTotal += value; } return runningTotal; }
This solution works, but is not extendable or modifiable. For example, how would you add only even numbers? Or how about adding a complete total, and then adding odd numbers and then even numbers to get three different totals? Of course, all of those solutions are possible, but they require modifying the proposed source code. There is a way to define an architecture where you can extend without having to constantly extend and maintain the code.
A functor is a mechanism where within the code you define a placeholder or functionality that acts as a pass-through filter before an actual functionality. The pass-through filter makes a client believe that they are using the real object without having to explicitly call a functor. For illustration purposes, I will quickly fix up the proposed code to use functors, but that is not how I would generally fix up the code. The illustration is meant to illustrate how a functor would be added to code that performs a specific task:
delegate void FunctorDelegate(int value); class Addition { FunctorDelegate _delegate; public Addition AddDelegate(FunctorDelegate deleg) { _delegate += deleg; return this; } public int AddAllElements(IList< int> list) { int runningTotal = 0; foreach( int value in list) { runningTotal += value; _delegate(value); } return runningTotal; } }
The method AddAllElements has changed its implementation; the addition is still part of the method, but so is the calling of the delegate. For every iteration of the loop, the running total will be calculated and the delegates associated with _delegate will be called. Of course, it is silly to add the numbers and call the delegate at the same time. What is being illustrated is that you still need to loop at the appropriate time the delegate is called.
The following example illustrates how to make use of the delegate call, where client code calls the method AddAllElements and calculates the odd and even number totals:
List< int> list = new List< int>(); list.Add(1); list.Add(2); list.Add(3); list.Add(4); int runningOddTotal = 0; int runningEvenTotal = 0; int runningTotal = new Addition() .AddDelegate(new FunctorDelegate( delegate(int value) { if ((value % 2) == 1) { runningOddTotal += value; } })) .AddDelegate(new FunctorDelegate( delegate(int value) { if ((value % 2) == 0) { runningEvenTotal += value; } })) .AddAllElements(list); Console.WriteLine( "Running total is (" + runningTotal + ") Running even total is (" + runningEvenTotal + ") Running odd total is (" + runningOddTotal + ")");
The preceding code can be compiled only using .NET 2.0 because anonymous delegates are used. At the beginning of the code piece is the variable list, which contains all of the integers that will be added. Then variables runningOddTotal, runningEvenTotal, and runningTotal are declared. After that the disposal type Addition is instantiated, and the method AddDelegate is called twice. A FunctorDelegate delegate is passed to AddDelegate by using the delegate keyword.
The delegate keyword used in conjunction with a method defines an anonymous delegate. The anonymous delegate requires a single parameter value, and is declared right after the delegate keyword. The curly brackets thereafter indicate the body of the function. Notice how the implementation of the anonymous delegate references variables declared in the body of the method. This is a very powerful use of anonymous functions because it allows us to declare functionality that references local variables that will be called later. In the two anonymous function declarations, the mod (%) operator determines if the value is even or odd.
To finish the explanation for the type, the method AddAllElements is called, adding and calling the delegates. The Console.WriteLine method outputs the generated values.
What you should be able to see is that a functor allows the original functionality to be kept intact, and additional functionality to be attached dynamically. Abstracting the logic, the addition of all numbers would have been an anonymous delegate implementation. Though if we want to think ahead and promote a good design for functors, we should be thinking about using a Proxy pattern.
The Proxy pattern is useful when you want to modify a type’s functionality without the blessing or cooperation of the type. Let’s say that we want to add all elements of a list. So instead of iterating the list repeatedly, we use the Proxy pattern to intercept method calls that will add and subtract numbers as elements are added and subtracted from a list:
interface IRunningTotal { int RunningTotal { get; } } class TotalCalculator<type>: ICollection<type> , IRunningTotal { private ICollection<type> _parent; private int _runningTotal; public TotalCalculator(ICollection<type> parent) { _parent = parent; _runningTotal = 0; } public int RunningTotal { get { return _runningTotal; } } public void Add(type item) { _runningTotal += int.Parse( item.ToString()); _parent.Add(item); } public bool Remove(type item) { bool retval = _parent.Remove( item); if( retval) { _runningTotal -= int.Parse( item.ToString()); } return retval; } // Other methods have been removed for clarity }
In the example code there are two types defined: IRunningTotal and TotalCalculator. TotalCalculator implements the ICollection<> interface and acts as a proxy to a list that implements ICollection<>. The interface IRunningTotal is implemented by TotalCalculator and used by the client to retrieve the running total of all the elements in the collection.
TotalCalculator implements the Proxy pattern because TotalCalculator contains a parent reference and delegates all method calls to the parent collection reference. In the example the TotalCalculator implements the ICollection interface, and the parent is an ICollection implementation. Then when the Add or Remove method is called the proxy will either add the amount to or subtract the amount from the running total, and call the parent. The client will not know the difference between the real ICollection implementation and the proxy, as the following example illustrates:
private void DoCollection( ICollection< int> list) { list.Add(1); list.Add(2); list.Add(3); list.Add(4); list.Remove( 4); } public void TestRunningTotalList() { ICollection< int> list = new TotalCalculator< int>( new List< int>()); DoCollection( list); Console.WriteLine( "Total is (" + ((IRunningTotal)list).RunningTotal + ")"); }
In the client code the method DoCollection performs some actions on the collection. The operations add elements and remove elements. In the method TestRunningTotalList the class TotalCalculator<> is instantiated, and an instance of a collection type List is passed to the constructor. When the method DoCollection is called, the parameter references an instance of the proxy. The method DoCollection is not aware that it is manipulating a proxy. After the method DoCollection has been called, a typecast to IRunningTotal retrieves the sum of the collection.
The proxy code works, and illustrates how the Proxy pattern can be used to inject functionality that was not planned originally. The Proxy pattern is similar to a functor, but is not a functor. The example Proxy pattern implementation is too specific and cannot be abstracted easily. A proper functor architecture uses the proxy and provides some extra plumbing for the application-specific logic.
Functors make it possible to attach standard logic to another type using a predefined construct. For example, when collections use Generics, Generics validates the type but does not validate if the contents of the type is correct. A predicate functor can validate the contents of the type. Functors are little black boxes that carry out specific pieces of logic. When implementing functors, you must use interfaces or abstract base classes; otherwise it is not possible to make one type act like another. The most common functor types are defined as follows:
- Comparer. This accepts two objects and compares them. The return value is an
intvalue, which represents whether the object is equal, less than, or greater than another value.
- Closure. This accepts a single object and performs some logic, and there is no returned value.
- Predicate. This accepts a single value and performs some logic. A
truevalue indicates that the predicate logic triggered, andfalsemeans that the predicate logic did not trigger.
- Transformer. This accepts a single value and performs a transformation, which could result in a new type being generated or the original type being transformed.
Functors are not specific to collection classes, but in most cases functors are used in conjunction with collections. For example, maybe you want to compare values and have the collection sorted whenever an element is added to the collection. Or maybe you want only to be able to filter objects that have certain properties constrained to certain values.
|

