Index
- Introduction
- Delegates
- Lambda expressions
- Events and callbacks
- References
Introduction
This post is part of a series of post to help you prepare the MCSD certification, particularly the certification exam 70-483, based on the book:You will find all the code available in the following GitHub repository. Lets talk a little bit about threads and how they work using a .Net development environment.
Delegates
Basically, delegates are pointers to a function, they define the signature of a method and helps you to abstract the call to a function from a class. This avoid tight coupling between classes. Imagine, you are using a math library by calling its functions from your user interface. If the library changes its implementation you will end changing you interface too but this shouldn't affect you at all. Then is when delegates are useful by defining an abstract place where you can make your math calls and not being affected by any change in the library (loose coupling).If you know how pointers work in C++ (no type safe), delegates are similar for .Net and also are type safe. See an example of how to DECLARE a new delegate, CREATE an instance of it, POINT the delegate to a particular function and INVOKE that function using the delegate:
Now I'd like to explain you the main features for delegates:
- Instantiation
- Multicasting
- Delegate family (MulticastDelegate)
- Covariance
- Contravariance
See how the "delegate" reserved word is used here to tell the compiler the type of what we're creating. Since C# 2.0 you can instantiate delegates and pass them around, even send it to a particular method.
Other feature of delegates is multicasting: by using "+" and "+=" you can add other methods to the invocation list of an existing delegate instance. It'd look something like this:
Delegate d = Method1;
d += Method2;
d += Method2;
You can also end removing method from the invocation list using "-" or "-=". All this is possible because delegates inherit from System.MulticastDelegate which in turn, inherits from System.Delegate. For example, you might want to see how many methods a multicast delegate is going to call by using:
int invocationCount = del.GetInvocationList().GetLength(0);
Finally, when you assign a method to a delegate, the method does not have to match the delegate exactly. This is called covariance and contravariance; both enable implicit reference conversion for arrays, delegates and generic types.
Covariance allows a method to return (Func) a type more derived than its hooked delegate. Contravariance permits a method that has a parameter (Action) types less derived than those in the delegate type.
See this covariance example where both StreamWriter and StringWriter inherit from TextWriter so the delegate will understand the returning type of the methods we hook in the example:
In the next example, you can see how contravariance works in a kind of similar way. This time the more derived class is in the delegate signature and because the method can work with TextWriter, should also know how to work with a StreamWriter:
Lambda expressions
This expressions, also called anonymous functions, are really handy when you need to create small functionality because you don't need to specify the entire method structure. You don't need to specify the return type or even the parameters type, see:Sometimes, declaring the delegate as we did in our example can look cumbersome. We might want to replace it using Func<T, Result> types which represents delegates that returns a value. It's provided by the System namespace and take from 0 to 16 params. We declare a Func<int, int, int> where the first two integers represent the input parameters and the third one represents the result type:
On the other hand, if you want a delegate which doesn't return anything, you might want to use the System.Action type which also takes from 0 to 16 params:
All of this get more complicated when, for instance, lambda expressions refers to variables outside of the lambda expression. Normally, when the control leaves the scope of a variable, this is no longer valid. But if a delegate refers to a local variable and is returned to the calling method, the delegate has a longer life that the variable. In this cases, .Net makes those variables alive as the longest living delegate. It's called closure.
Practically, a closure is a block of code which can be executed at a later time, but which maintains the environment in which it was first created. It can use local variables of the method where was created, even if the method is finished. It's implemented by anonymous methods (lambda exp) like here:
See how the counter is created outside the delegate and then we return that delegate with the counter inside. The compiler needs to give a longer life to the variable, that's why this is called closure.
Events and callbacks
Events are a popular design pattern you can subscribe to, then you are notified when the publisher raises a new event (loose coupling). Delegates are the base of events. See here below how to instantiate a class and subscribe to its OnChange property which is raised only (check the if statement) when someone has subscribed to it through the Action delegate:
There is a weakness in the previous algorithm and it's because every user of the class can raise the event to all subscribers. To overcome this weakness we have in C# the event keyword. By using the event type we'll be solving:
There is a weakness in the previous algorithm and it's because every user of the class can raise the event to all subscribers. To overcome this weakness we have in C# the event keyword. By using the event type we'll be solving:
- No longer a public property but a public field (compare with the previous example). This looks like a step back but the compiler will protect the event from unwanted access.
- The event cannot be assigned the same way as a delegate ( = / +=) which prevents anyone removing previous subscriptions.
- No outside users can raise the event. Only code that's part of the class defined the event.
- null check can be removed because the event is initialized.
- To match the .Net convention we need to replace the Action type and use an EventHandler or EventHandler<T> type
Bear in mind the EventHandler takes a sender object (who raised the event. Null if a static method raised it) and some events arguments. This is how the code would look like after all these modifications:
See how we've customized the EventArgs so subscribers are required to pass an instance of the class. We can customize addition and removal of subscribers (aka custom event accessor). It looks like a property with a get and set accessor, but instead of "get / set" you use "add / remove". Important the "lock" keyword to make the access to the event thread safe, see how:
Think about events like a wrapper around delegates. Delegates are executed in a sequential order, generally, in order they were added. This take us to the next step: handling exceptions.
For example, if you subscribe three methods and the second one raises an Exception, you will probably get the result of the first method, while the third one won't be executed. If this is not the behaviour expected you can customize by using GetInvocationList() method and retrieving the subscribers and enumerating them manually, ie:
In the previous example we've customized how exceptions are managed so we always run any raised event but we've also logged if an exception happened.
I know, I know, today was a long post for a long day but if you understand how lambda expressions, delegates and events work together you will find yourself in a really good position to start making good and loose coupled applications ;) See ya!
0 comments:
Post a Comment