C# delegates and generics
We can consider C# delegates as type safe pointers to methods.Take a simple delegate SomeAction which takes a generic type and returns nothing.
delegate void SomeAction< T >( List< T > input);
We can use this delegate to refer to any method which has the same method signature.In the code below an instance of SomeAction
static void PrintList< T >( List< T > inList )
{
inList.ForEach( ( i ) => { Console.Write( i + " " ); } );
Console.WriteLine();
}
List< int > test = new List< int >(){1, 2, 3, 4, 5};
SomeAction< int > PrintIntList = PrintList;
PrintIntList( test );
//Output: 1 2 3 4 5
A map method in C# using delegate
Consider a map function, which takes as arguments a list and another function. It applies the function to all members of the list, to produce an output list. A naive implementation in Scheme could be:
(define (map f inlist)
(if (null? inlist)
'()
(cons (f (car inlist)) (map f (cdr inlist)))))
So, if we want to apply a function which doubles a list:
(map (lambda (i) (* 2 i)) '(1 2 3 4 5))
;output => '(2 4 6 8 10)
(A note on implementing map in Scheme is here)To implement a similar map method in C#, which operates on a List:
delegate T GenericDelegate< T >( T args );
static List< T > Map< T >( List< T > inList, GenericDelegate f )
{
List< T > outList = new List< T >();
for ( int i = 0; i < inList.Count(); i++ ) {
outList.Add( f( inList[i] ) );
}
return outList;
}
List< int > test = new List< int >(){1, 2, 3, 4, 5};
List< int > doubleTest = Map( test, ( i ) => { return 2 * i; } );
doubleTest.ForEach( ( i ) => { Console.Write( i + " " ); } );
//2 4 6 8 10
Action and Func
These are just syntactic sugar in C# for generic delegates.We could use this in place of the generic delegate we used in the previous section:
delegate TResult Func< in T, out TResult >(T arg);
So we could implement the map method using a Func overload as:
static List< T > MapWithFunc< T >( List< T > inList, Func< T, T > f )
{
List< T > outList = new List< T >();
for ( int i = 0; i < inList.Count(); i++ ) {
outList.Add( f( inList[i] ) );
}
return outList;
}
List< int > test = new List< int >(){1, 2, 3, 4, 5};
List< int > doubleTest = MapWithFunc< int >( test, ( i ) => { return 2 * i; } );
doubleTest.ForEach( ( i ) => { Console.Write( i + " " ); } );
//2 4 6 8 10
Action is a limited form of Func, it only represents a delegate which takes argument(s) and does not return anything. For example:
delegate void Action< in T > (T arg);
C# provides Func and Action for 1 to 16 arguments, which the language designers think should be enough for most cases.C# Action, practical use
Consider a solution with a hierarchy of projectsSolution
|
|
---Common
|
|
---Web (References Common)
At times, we would like classes in Common to call methods in the Web project. However, by design, we do not want Common to reference the Web project. We can solve this problem by using callbacks, which in C# are Action or Func (or delegates).
Let us say there is a class in the Common, CommonUtil with a method UsefulMethod
namespace Common {
public class CommonUtil {
//...
public void UsefulMethod(string someArg, Action< string > syslogOnError) {
//...
syslogOnError( "Some common error in the context of the caller" );
Now in the Web project, we call the UsefulMethod from a Controller class:
using Common;
namespace Web {
public class TestController : BaseController {
CommonUtil util = new CommonUtil();
util.UsefulMethod("abc", new SyslogControllerDelegate( this ).Syslog());
//...
Where the Syslog method would use the controller to add extra context to a syslog message:
namespace Web {
public BaseController controller;
//...
public Action< string > Syslog() {
Action< string > syslogAction = delegate( string mesg ) {
//Writes controller context in addition to mesg to the syslog
new SyslogBuilder( controller ).Info( mesg );
}
}
}
So when UsefulMethod calls its syslogOnError, in addition to the Common error we would also get whatever extra context SyslogBuilder wants to print about the calling TestController.
Note that Common did not have to reference the Web project to do this, it just used a callback provided by the calling Web class.
Thanks for posting this. It was very informative.
ReplyDelete