Professional Documents
Culture Documents
Contents
1. Introduction .............................................................................................. 3 2. Delegates in a Basic Way (C# 1) .................................................................. 4 3. Delegates in a Simple Way (C# 2) ................................................................ 6 4. Delegates with LINQ (C# 3) ........................................................................ 8 5. Invoking Multiple Methods ......................................................................... 10 6. Observer Pattern ...................................................................................... 12 7. Events .................................................................................................... 18 8. Actions, Predicates, and Functions .............................................................. 21 9. Asynchronous Delegates ........................................................................... 25
1. Introduction
Have you ever heard of function pointers? If you have had a chance to study C or C++, you know what a pointer is. Just like the pointer to a value or an object, there is a pointer to a function. A function pointer has a reference to the functions entry point so that you can invoke the function not by a function name but by a pointer variable. A function pointer is very useful when you use the call-back feature. JavaScript uses it very often. For example, when you make an asynchronous call to the server, you pass the handler functions pointer with the request call. When the server is ready to respond, the server just calls the handler using the function pointer.
In C#, this functionality is accomplished by delegates. A delegate is an object that points to another method (both static and instance). It manages the address of (or a reference to) a method, a list of arguments, and a return value.
Delegates (C#1, C#2, and C#3) Events Lambda Expressions Observer Pattern
public static class IntFun { public static int GetTotal(int x, int y, int z) { return (int)(x + y + z); } public static int GetAverage(int x, int y, int z) { return (int)((x + y + z) / 3); } }
Note that the name of the method is passed as an argument to the delegate type constructor.
Console.WriteLine("Total = {0}", fp(10, 20, 30)); // 60 Console.WriteLine("Average = {0}", fp1(10, 20, 30)); // 20
public delegate T Adder<T>(T x, T y); public static class GenericFun { public static int AddInt(int x, int y) { return x + y; } public static string AddNumber(string x, string y) { double a = Double.Parse(x); double b = Double.Parse(y); return (a + b).ToString(); } } public static class DelegateTest { public static void Test() { Adder<int> fp = new Adder<int>(GenericFun.AddInt); Adder<string> fp1 = new Adder<string>(GenericFun.AddNumber); Console.WriteLine("2 + 3 = {0}", fp(2, 3)); // 5 Console.WriteLine("1.4 + 4.7 = {0}", fp1("1.4", "4.7")); } }
// 6.1
C# 2 introduced a new syntax sugar. You can create a delegate instance implicitly.
Adder<int> fp = GenericFun.AddInt;
The only thing you need to care is that you cannot specify a return type. Just make sure you return the right type in the method body. The anonymous method works with generic types too.
public delegate T Adder<T>(T x, T y); public static void TestAnonymous() { Adder<int> fp = new Adder<int>(delegate(int x, int y) { return x + y; }); Console.WriteLine("2 + 3 = {0}", fp(2, 3)); // 5 }
(x, y) => x == y (string s, int x) => (s.Length <= x) If you do not specify the type of a parameter, the compiler will infer the type.
s => { Console.WriteLine(s); }
2012 by Pyongwon Lee
4.1.3 Example Lets rewrite the generic anonymous method using a Lambda operator.
public delegate T Adder<T>(T x, T y); public static void TestLambda() { Adder<int> fp = (x, y) => { return x + y; }; Console.WriteLine("2 + 3 = {0}", fp(2, 3)); // 5 }
10
public delegate void Greet(); public static class GreetFun { public static void Test() { Greet fp = () => { Console.WriteLine("Hello"); }; fp += () => { Console.WriteLine("Nice weather!"); }; fp += () => { Console.WriteLine("Bye"); }; fp(); // "Hello Nice weather! Bye" } }
You can use the following code to see the list of methods that the delegate holds. Note that the Delegate class has 2 properties:
11
public delegate T DoSomething<T> (T x, T y); public class DelegateTest { public static int AddInt(int x, int y) { return x + y; } public int SubtractInt(int x, int y) { return x - y; } public static void InvestigateDelegate() { DelegateTest test = new DelegateTest(); DoSomething<int> fp = DelegateTest.AddInt; // static fp += test.SubtractInt; // instance fp += (x, y) => { return x * y; }; // anonymous, static foreach (Delegate d in fp.GetInvocationList()) { Console.WriteLine("Type = {0}, Method = {1}", d.Target, d.Method); } } }
12
6. Observer Pattern
Before moving on to the Events, lets implement the Observer pattern.
Delegates are excellent choice for a publisher or a subject because it can hold multiple methods and call them at once.
13
{ public int stockIndex = 0; public int StockIndex { get { return stockIndex; } set { stockIndex = value; // Notify to the subscribers } } } // Subscriber public class Investor { public void Notified(int stockIndex) { Console.WriteLine("From a Investor: Got it! The index is {0}", stockIndex); } }
When the stock index is changed, the publisher (stock market) wants to notify the change to its subscribers (investors). To accomplish this, the following features will be implemented. A publisher needs to take care of a list of subscribers. Subscribers can subscribe to or unsubscribe from the notification list. A publisher sends a notification message to all subscribers whenever the notification is required.
If you specify a specific class type when you create a list of subscribers, you are restricted to use only one type of objects as a subscriber. So it is a good idea to use an abstract class or, better, an interface. What if financial advisors also want to get a current stock index?
// Subscriber public interface IStockNotifiable { void Notified(int stockIndex); } public class Investor : IStockNotifiable { public void Notified(int stockIndex) { Console.WriteLine("From a Investor: Got it! The index is {0}", stockIndex); } } public class FinancialAdvisor : IStockNotifiable
2012 by Pyongwon Lee
14
{ public void Notified(int stockIndex) { Console.WriteLine("From a Financial Advisor: Got it! The index is {0}", stockIndex); } }
OK, now the publisher class needs to have a list of subscribers and provide a way to add/remove a subscriber to/from the list.
// Publisher public class StockMarket { private List<IStockNotifiable> subscribers = new List<IStockNotifiable>(); public void Subscribe(IStockNotifiable subscriber) { subscribers.Add(subscriber); } public void Unsubscribe(IStockNotifiable subscriber) { subscribers.Remove(subscriber); } }
Finally, when the stock index is changed, the publisher should notify the change to subscribers.
// Publisher public class StockMarket { public int stockIndex = 0; public int StockIndex { get { return stockIndex; } set { stockIndex = value; // Notify to the subscribers foreach (IStockNotifiable subscriber in subscribers) { subscriber.Notified(stockIndex); } } } }
15
If you are familiar with the LINQ syntax, you can use the code this like:
set { stockIndex = value; // Notify to the subscribers subscribers.ForEach(s => s.Notified(stockIndex)); }
Note that how the lambda expression can be used with the LINQ operators.
So far you have created interfaces and classes like this. Compare this with the <Figure 1>.
public static class Tester { public static void Test() { StockMarket market = new StockMarket(); Investor investor1 = new Investor(); market.Subscribe(investor1); Investor investor2 = new Investor(); market.Subscribe(investor2); FinancialAdvisor investor3 = new FinancialAdvisor(); market.Subscribe(investor3); // Stock Index is changed. market.StockIndex = 100;
2012 by Pyongwon Lee
16
Inside the publisher class, an instance of a delegate is created instead of a list of subscribers. The Subscribe() and Unsubscibe() methods are also modified to use the delegate object.
// Publisher public class StockMarket { private StockIndexHandler subscribers = null; public void Subscribe(IStockNotifiable subscriber) { subscribers += subscriber.Notified; } public void Unsubscribe(IStockNotifiable subscriber) { subscribers -= subscriber.Notified; } }
Finally you can use the delegate object to notify the change to all subscribers.
17
// Publisher public class StockMarket { public int stockIndex = 0; public int StockIndex { get { return stockIndex; } set { stockIndex = value; // Notify to the subscribers subscribers(stockIndex); } } }
When you run the code, the result will be the same.
18
7. Events
Because the Observer pattern (with delegates) can be very useful in the event handling model of the UI programming, .Net Framework wanted to make it formal. In the event handling model, the event belongs to a publisher and handlers are subscribers. C# provides the event keyword. It is used with the delegate type. You can think that an event is a modifier to the delegate instance. The important thing to remember is that a delegate is a type like a class but an event is a field (rather a property) and should be located in a type.
public delegate void StockIndexHandler(int stockIndex); public class StockMarket { public event StockIndexHandler stockEvent; }
When you add the event keyword to a delegate field, the compiler does some additional actions: The complier creates addhandler and removehandler methods linked to the delegate type internally You can include a delegate instance in an interface (An interface cannot have a field). But an event can be included in an interface. Event handler invocation is restricted within the class that declares an event.
public delegate void StockIndexHandler(int stockIndex); public class StockMarket { public event StockIndexHandler stockEvent; public StockIndexHandler stockDelegate; private int stockIndex; public int StockIndex { get { return stockIndex; } set { stockEvent(stockIndex); } // ok } } public class EventFun {
2012 by Pyongwon Lee
19
public static void Test() { StockMarket market = new StockMarket(); market.stockEvent(100);// this line casuses an compile time error market.stockDelegate(100); // ok } }
public delegate void StockIndexHandler(int stockIndex); public interface IStockNotifiable { void Notified(int stockIndex); } public class Investor : IStockNotifiable { public void Notified(int stockIndex) { Console.WriteLine("From a Investor: Got it! The index is {0}", stockIndex); } } public class FinancialAdvisor : IStockNotifiable { public void Notified(int stockIndex) { Console.WriteLine("From a Financial Advisor: Got it! The index is {0}", stockIndex); } } public class StockMarket { public event StockIndexHandler stockEvent; public int stockIndex = 0; public int StockIndex
2012 by Pyongwon Lee
20
{ get { return stockIndex; } set { stockIndex = value; if (stockEvent!= null) { stockEvent(stockIndex); // call event handlers } } } public void Subscribe(IStockNotifiable subscriber) { stockEvent += subscriber.Notified; } public void Unsubscribe(IStockNotifiable subscriber) { stockEvent -= subscriber.Notified; } } public static class EventFun { public static void Test() { StockMarket market = new StockMarket(); Investor investor1 = new Investor(); market.Subscribe(investor1); Investor investor2 = new Investor(); market.Subscribe(investor2); FinancialAdvisor investor3 = new FinancialAdvisor(); market.Subscribe(investor3); // Stock Index is changed. market.StockIndex = 100; // Unsubscribe market.Unsubscribe(investor2); // StockPrice is changed. market.StockIndex = 95; } }
21
8.1 Actions
An action delegate encapsulates a method that does not return a value. .NET Framework provides 17 Action delegates whose parameters differ.
delegate void Action() delegate void Action<in T>(T arg) delegate void Action<in T1, in T2>(T1 arg1, T2 arg2) delegate void Action<in T1, in T2, ..., in T16>(T1 arg1, T2 arg2, ... , T16 arg16)
Generics play a very important role here. It is not practical to create delegate for all possible data types. All Action delegates are declared in the System namespace.
public static void SayHello(bool isWinForm) { Action<string> display; if (isWinForm) display = (s => { System.Windows.Forms.MessageBox.Show(s); }); else display = (s => { Console.WriteLine(s); }); display("Hello, World"); } public static void Test()
2012 by Pyongwon Lee
22
{ SayHello(false); SayHello(true); }
8.2 Functions
A function delegate encapsulates a method that returns a value. 17 Func delegates are defined in .NET Framework.
public delegate public delegate public delegate . . . public delegate arg2, ... , T16
TResult Func(out TResult) TResult Func<in T, out TResult>(T arg) TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2) TResult Func<in T1, in T2, ..., in T16, out TResult>(T1 arg1, T2 arg16)
public enum OperationType { Add, Subtract, Multiply } public static void DoMath(OperationType type, int x, int y) { Func<int, int, string> operation; switch(type) { case OperationType.Add: operation= ((i, j) => { return break; case OperationType.Subtract: operation= ((i, j) => { return break; case OperationType.Multiply: operation= ((i, j) => { return break; default: operation= ((i, j) => { return break; }
"N/A"; } );
23
Console.WriteLine(operation(x, y)); } public static void Test() { DoMath(OperationType.Add, 10, 5); DoMath(OperationType.Subtract, 10, 5); DoMath(OperationType.Multiply, 10, 5); }
8.3 Predicates
A predicate is a special kind of functions that return bool. Therefore .NET Framework does not provide exhaustive number of predefined predicate delegates. In fact, there is only one delegate.
public static IEnumerable<TSource> Where<TSource>( this IEnumerable<T> source, Func<TSource, bool> predicate ); public static IEnumerable<TResult> Select<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, TResult> selector );
24
Both Where and Select operators require the Func delegate as a parameter. In most cases, you are providing an anonymous method using the lambda expression.
var numbers = Enumerable.Range(1, 10); var oddNumbers = numbers.Where(i => i % 2 == 1).Select(i => i); foreach (int i in oddNumbers) Console.WriteLine(i);
25
9. Asynchronous Delegates
By default, delegates work synchronously. But you can use them asynchronously too. You do not even need to work with any types of System.Threading namespace.
public delegate int MathOp(int x, int y); public static class AsynchPattern { public static void Test() { MathOp addOp = ((x, y) => x + y); addOp. } }
26
public IAsyncResult BeginInvoke(int x, int y, AsyncCallback cb, object state); public int EndInvoke(IAsyncResult result);
If you look at the documentation of Delegate or MulticastDelegate, BeginInvoke() and EndInvoke() method do not exist. So what are they? When you create a delegate type, the compiler automatically adds these methods using the signature of the delegate type.
public interface IAsyncResult { Object AsyncState { get; } // the last parameter of BeginInvoke() WaitHandle AsyncWaitHandle { get; } // to wait for an asynchronous operation to complete bool CompletedSynchronously { get; } // false for asynchronous operation bool IsCompleted { get; } }
27
public static void Test() { MathOp addOp = ((x, y) => { System.Threading.Thread.Sleep(4000); return x + y; }); IAsyncResult result = addOp.BeginInvoke(10, 20, null, null); while (!result.IsCompleted) { // Wait until the operation is completed Console.WriteLine("Waiting..."); System.Threading.Thread.Sleep(1000); } int answer = addOp.EndInvoke(result); Console.WriteLine("{0} + {1} = {2}", 10, 20, answer); }
Easy, isnt it? But if we just need to wait until the operation ends what ,in the first place, do we need to use an asynchronously call?
It is a delegate type that represents a method to be called when a corresponding asynchronous operation completes. When the call-back method is called, the System.Runtime.Remoting.Messaging.AsyncResult object is passed as an IAsyncResult argument.
28
using System.Runtime.Remoting.Messaging;
public static void Test1() { MathOp addOp = ((x, y) => { System.Threading.Thread.Sleep(4000); return x + y; }); IAsyncResult result = addOp.BeginInvoke(10, 20, ar => { AsyncResult aResult = ar as AsyncResult; MathOp op = aResult.AsyncDelegate as MathOp; int answer = addOp.EndInvoke(ar); Console.WriteLine("{0} + {1} = {2}", 10, 20, answer); }, null); // Doing some work here Console.WriteLine("Doing Something..."); System.Threading.Thread.Sleep(5000); Console.WriteLine("Done..."); }