Since I recently posted that I wasn't very fond of static classes and static constructors in C#, I wanted to even out my C# programming karma by mentioning a language feature in C# that I find fairly interesting. C# includes a language feature known as
delegation. Although I haven't used this language feature much myself, I have seen it used on a number of occasions in a fairly wide range of circumstances. The most common usage has been event handling associated with GUI components. This
Sun Develop Network (SDN) whitepaper discusses why inner classes were chosen in favor of delegation in the Java language. While this is an interesting whitepaper and topic, I don't really want to get into the GUI component aspects.
I do like both the Wikipedia and MS examples of how to do multicast delegation in C#. Below is the example code from Wikipedia (Note -- I removed some of the whitespace).
Code:
void HowAreYou(string sender) { Console.WriteLine("How are you, " + sender + '?'); }
void HowAreYouToday(string sender) { Console.WriteLine("How are you today, " + sender + '?'); }
Notifier greetMe;
greetMe = new Notifier(HowAreYou);
greetMe += new Notifier(HowAreYouToday);
greetMe("Alfred"); // "How are you, Alfred?"
// "How are you today, Alfred?"
greetMe -= new Notifier(HowAreYou);
greetMe("Anastasia"); // "How are you today, Anastasia?"
I can see this language feature being used in a number of interesting ways. For example, I might want to know the properties of a sequence of numbers. I could easily make a delegate with functions that inform me if a number is prime, relatively prime, perfect, Karprekar or any number of interesting things. Adding and removing functions can be handled at runtime; Modifying the delegate is as simple as += and -=. The possibilities seem to be fairly broad.
The one thing that I've noticed in the examples is that delegation seems to be most useful when you're working for side-effects (eg GUI, I/O, etc). I don't want anyone to think that this is function composition in which the result of one function is passed as input to another function. This is simply a convenient method of chaining together function/method calls.
As with many language features that I find interesting, I decided to see if I could implement a useful example in Common Lisp. I wrote a macro that creates three functions: one for calling the delegate, one for adding functions to the delegate, and one for removing old functions from the delegate. A closure is used to keep the delegate functions in a list. The delegate function simply iterates through the list calling each function in turn. I chose a macro implementation over a normal function implementation so that the functions are placed in the function namespace [Don't worry about that last statement if you're not a Lisp, Scheme, Ruby, Clojure type who understands the difference between a Lisp-1 and Lisp-2]. The CL macro code is also more general than the C# code. For example, you can have delegates that take one parameter, multiple parameters, a combination of parameters, and even parameters of different types (assuming the functions handle these calls properly).
Code:
;;Note - name, add-name and rem-name are the names of the functions that are created
;;(ie name would be equivalent to the delegate variable in C#, add-name to += and rem-name to -=)
;;Note2 - I could have written the macro so that add-name and rem-name done automatically (eg foo, add-foo, rem-foo),
;;but this would have complicated the macro code and I wanted it to be accessible to someone interested in learning macros
(defmacro make-delegate (name add-name rem-name &rest fns)
`(let ((fn-list (list ,@fns))) ;The list closure
(defun ,name (&rest params) ;The delegate
(dolist (fn fn-list) (apply fn params)))
(defun ,add-name (&rest fns) ;The += function
(setf fn-list (append fn-list fns)))
(defun ,rem-name (&rest fns) ;The -= function
(setf fn-list (set-difference fn-list fns)))))
;;Single numeric parameter
(defun sine (n) (format t "sin(~d) = ~d~%" n (sin n)))
(defun cosine (n) (format t "cos(~d) = ~d~%" n (cos n)))
(defun tangent (n) (format t "tan(~d) = ~d~%" n (tan n)))
;;Mulitple numeric parameters
(defun add (&rest vals) (format t "Adds to ~d~%" (apply #'+ vals)))
(defun sub (&rest vals) (format t "Subs to ~d~%" (apply #'- vals)))
;;Example...
CL-USER> (make-delegate foo add-foo rem-foo #'sine #'cosine #'tangent)
CL-USER> (foo 1)
sin(1) = 0.84147096
cos(1) = 0.5403023
tan(1) = 1.5574077
CL-USER> (rem-foo #'tangent)
CL-USER> (foo 1)
sin(1) = 0.84147096
cos(1) = 0.5403023
CL-USER> (rem-foo #'cosine #'sine)
CL-USER> (add-foo #'add #'sub)
CL-USER> (foo 10 5 1)
Adds to 16
Subs to 4
Pretty cool stuff... =)
I'll add another post tomorrow on some uses of delegates in C# that I don't find very appealing.