Evolution of C# delegates
-
Upload
mbaric -
Category
Technology
-
view
518 -
download
2
description
Transcript of Evolution of C# delegates
Marko BarićSenior software engineer @ Siemens [email protected]
Evolution of C# delegates
Agenda
• Basic delegates• Delegate types, delegate instances
• Combining multiple delegates• Method group conversions• Covariance and contravariance• Inline delegate actions• Captured variables (closures)• Lambda expressions
• Provide a level of indirection• Sort of "sigle method interface"• Delegates inhert from
System.Delegate
• To create and use delegates:• The delegate type needs to be declared• The code to be executed must be contained
in a method• A delegate instance must be created• The delegate instance must be invoked
What is a delegate?
delegate void StringProcessor(string input);
Declaring delegate types
keyword returntype
delegate type name arguments
class MyClass{ public void SomeMethod(string input) public static void SomeMethodStatic(string input)}
StringProcessor p1 = new StringProcessor(myClassInstance.SomeMethod);
StringProcessor p2 = new StringProcessor(MyClass.SomeMethod);
Creating delegate instances
delegate type
delegate instance variable
action to invoke on MyClass instance
static action to invoke on MyClass
StringProcessor p1 = new StringProcessor(myClassInstance.SomeMethod);
p1.Invoke("Some string");
// ...or shorter version p1("Some string");
Invoking delegates
p1("Some string");
p1.Invoke("Some string")
SomeMethod("Some string")
• Delegates can be treated like any other type• They have methods, multiple instance can be created...
delegate void StringProcessor(string input);class Person{ private string name; public Person(string name) { this.name = name; } public void SayTheMessage(string message) { Console.WriteLine("{0} says: {1}", name, message); } public static void SayTheMessageStatic(string message) { Console.WriteLine(message); } }
static void Main(string[] args){ Person john = new Person("John"); Person tom = new Person("Tom"); StringProcessor johnsVoice = new StringProcessor(john.SayTheMessage); StringProcessor tomsVoice = new StringProcessor(tom.SayTheMessage); StringProcessor someonesVoice = new StringProcessor(Person.SayTheMessageStatic);
johnsVoice("Hello!"); tomsVoice.Invoke("Hello!"); someonesVoice("Just saying something...");}
Example
// Result: "John says: Hello!""Tom says: Hello!""Just saying something..."
• All delegates inherit methods from System.Delegate:• Delegate.Combine()• Delegate.Remove()
• Every delegate has an invocation list - a list of actions to invoke
• "+", "+=", "-", "-="• When delegate is invoked, all
actions in the invocation list are invoked in the order they were added
Combining the delegates
Person john = new Person("John");Person tom = new Person("Tom");Person mike = new Person("Mike");
StringProcessor johnsVoice = new StringProcessor(john.SayTheMessage);StringProcessor tomsVoice = new StringProcessor(tom.SayTheMessage);StringProcessor mikesVoice = new StringProcessor(mike.SayTheMessage);
StringProcessor twoCombined = johnsVoice + tomsVoice;StringProcessor allCombined = twoCombined + mikesVoice;allCombined += new StringProcessor(john.SayTheMessage);
allCombined("What's up!");
// Result: "John says: What's up!""Tom says: What's up!""Mike says: What's up!""John says: What's up!"
Example of combining delegates
//Delegate typedelegate void KeyPressEventHandler(object sender, KeyPressEventArgs e);
//Target actionvoid LogKeyEvent(object sender, KeyPressEventArgs e){ /* do something */ }
void LogKeyEvent(int x){ /* do something */ }
button.KeyPress += new KeyPressEventHandler(LogKeyEvent);
button.KeyPress += LogKeyEvent;
Method group converions
Method group is converted to compatible delegate type
Click -----> void EventHandler(object sender, EventArgs e)KeyPress -----> void KeyPressEventHandler(object sender, KeyPressEventArgs e)MouseClick ---> void MouseClickEventHandler(object sender, MouseEventArgs e)
Button button = new Button();button.Text = "Click me";button.Click += LogPlainEvent;button.KeyPress += LogPlainEvent;button.MouseClick += LogPlainEvent;
Form form = new Form();form.Controls.Add(button);Application.Run(form);
static void LogEvent(object sender, EventArgs e){ Console.WriteLine("An event occurred");}
Contravariance of delegates
Same action for 3 different delegates!
delegate Stream MyDelegate();
static MemoryStream GenerateSampleData(){ byte[] buffer = new byte[16]; return new MemoryStream(buffer);}
MyDelegate d = GenerateSampleData();Stream stream = d();MemoryStream stream = d();
Covariance of delegates
Stream
MemoryStream
Built-in delegate types in .NET
delegate void Action<T>(T arg);delegate void Action<T1, T2>(T1 arg1, T2 arg2);...
delegate TResult Func<T, TResult>(T arg);delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);...
delegate bool Predicate<T>(T arg);...
• You don't need action method to exist - you can create it inline
Inline delegate actions
Action<int> printRoot = delegate(int number){ Console.WriteLine(Math.Sqrt(number));};
printRoot(9);
Func<string, int> getLength = delegate(string input){ return input.Length;};
int length = getLength("Some string");
Invoke it like other delegates
button.Click += delegate(object sender, EventArgs e) { ... };
Button button = new Button();button.Text = "Click me";button.Click += delegate { Console.WriteLine("Click"); };button.KeyPress += delegate { Console.WriteLine("KeyPress"); };button.MouseClick += delegate { Console.WriteLine("MouseClick"); };
Ingnoring inline delegate arguments
• When you won't use delegate arguments you can loose them in definition
• Beware of the compiler limitations:
Ingnoring inline delegate arguments
// Thread class has several different constructorspublic Thread(ThreadStart start)public Thread(ParameterizedThreadStart start)
// There are 2 types of delegates involvedpublic delegate void ThreadStart()public delegate void ParameterizedThreadStart(object obj)
new Thread(delegate() { Console.WriteLine("Something..."); } );new Thread(delegate(object o) { Console.WriteLine("Something..."); } );new Thread(delegate { Console.WriteLine("Something..."); } );
Captured variables (closures)
• Captured variables are outer variables used (captured) in the scope of anonymous methodvoid EnclosingMethod(){ string outerVariable = "Default string";
Action<int> a = delegate() { string localVariable = outerVariable; };
a();}
delegate void MyDelegate();
string captured = "before x is created";MyDelegate x = delegate{ Console.WriteLine(captured); captured = "changed by x";};captured = "before x is invoked";x();Console.WriteLine(captured);captured = "before second invocation";x();
// Result:"before x is invoked""changed by x""before second invocation"
Captured variables
The captured variable is the same one that the outer code uses!!!
• A captured variable lives for at least as long as any delegate instance referring to it
Lifetime of captured variables
public MethodInvoker CreateDelegateInstance(){ int counter = 5; MethodInvoker increment = delegate { Console.WriteLine(counter); counter++; };
increment(); return counter;}...
MethodInvoker x = CreateDelegateInstance();x();x();
MethodInvoker[] invokers = new MethodInvoker[2];
int outsideVariable = 0;for (int i=0; i<2; i++){ int insideVariable = 0; invokers[i] = delegate { Console.WriteLine ("({0},{1})", outsideVariable, insideVariable); outsideVariable++; insideVariable++; };}MethodInvoker first = invokers[0];MethodInvoker second = invokers[1];
first();first();first();second();second();
Things can get tricky very fast
// Result:(0,0)(1,1)(2,2)(3,0)(4,1)
• They are evolution of anonymous methods
• More readable and compact than other delegate forms
• Many shortcuts and "syntatic sugar" tricks allow most compat form of code
• Brings new operator "=>" (spelled as "goes to")
Lambda expressions
delegate TResult Func<T, TResult>(T input);
Func<string, int> returnLength;returnLength = delegate (string text) { return text.Length; };
returnLength = (string text) => { return text.Length; };
Simple lambda expression
input arguments statements
(list of input arguments) => { statements }
returnLength = (string text) => { return text.Length; }
returnLength = (string text) => text.Length
returnLength = (text) => text.Length
returnLength = text => text.Length
Shortening the lambdas
If the statement is single expression, you can loose the braces, return statement and semicolon
Compiler can guess type of input arguments,so you can loose it
If there is single input argument, you can loose the parentheses
Let's recap
Func<string, int> returnLength = new Func<string, int>(GetLength);returnLength(text);
Func<string, int> returnLength = GetLength;returnLength(text);
returnLength = delegate (string text) { return text.Length; };
returnLength = (string text) => { return text.Length; };
returnLength = text => text.Length;
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate){ if (source == null || predicate == null) { throw new ArgumentNullExcpetion(); }
foreach (T item in source) { if (predicate(item)) { yield return item; } } }
// Usage:var items = new List<string> { "John", "Tom", "Mike" };var filteredItems = items.Where(i => i.StartsWith("J"));
Real-life lamba example - Where
Real-life lambdas in action
Q & A?
Thank you!