Deep C# - Anonymous Methods, Lambdas And Closures |
Written by Mike James | ||||||
Monday, 19 September 2016 | ||||||
Page 5 of 5
Using Closure 1 - More parametersClearly closures are fun but what use are they? The answer is that they provide a context for a function which can be used to provide it with additional information without the need to use additional parameters. Why wouldn’t you create some additional parameters? Most likely because the signature of the function you are trying to use isn’t under your control. For example consider the EnumWindows API call which needs a callback function that is called for each window that it enumerates. The API call is:
and the callback delegate is:
The problem with using the callback delegate is that it only has the two parameters - the handle of the current window and a pointer supplied in the call to the EnumWindows function. It is this pointer that is used to communicate between the callback function and the program needing the enumeration. Closure, however, makes communication much easier. If you need a function to find a particular dialog box specified by its Owner and its Caption string then you could write a function something like:
Clearly we need to pass the Owner and Caption to the callback delegate so that it can compare each of the enumerated windows to the target. The most natural way to do this is to provide these extra pieces of information to the callback via extra parameters but to work as a callback it can only have the parameters defined by the API. The simplest solution is to define the callback delegate in the usual way but use the fact that the Owner and Caption parameters are in scope and so are captured by an anonymous function:
First we get the window text and compare it to Caption, which we can only access here thanks to closure:
If they match we check that the class name is correct for a dialog box and then check that Owner, which is once again only accessible at this point because of closure, is the correct window:
This completes the anonymous callback delegate; now we can call EnumWindows:
To follow what is going on here you need to keep in mind that the callback is actually run by the API via the call
which passes in the handle to the window and a pointer to soon additional data. The Owner and Caption variables are used within the callback courtesy of closure. Notice that the pointer in the callback delegate is used to return the handle of the dialog box that we have found but this too could have been achieved using closure. Without any use of closure we would have had to pack the Owner and Caption into a data structure and passed this to the callback. Closure makes things much simpler in this case. Using Closure 2 - Event patternThere is one very important situation where closure simplifies things - although not everyone agrees that it is a good idea. In an event oriented environment the linear flow of actions is often made unclear by the need to pause processing until an event occurs. Situations like this occur all the time but to make the explanation easier to follow let's try to remain as general as possible Suppose you are writing some code that works with some resources and you get to the point where you need to load a new resource and this is a time consuming action then there are two approaches blocking synchronous and non-blocking asynchronous. Blocking synchronous simply causes the thread doing the work to pause until the resource is loaded:
This has the advantage of simplicity and you can clearly see what is happening but if the processing thread is also processing events then all event handling stops at the wait. This is a common problem that can be partly alleviated by use of constructs such as DoEvents which sends the thread off to process events while waiting:
This approach has many problems - the biggest being reentrancy, i.e. what if an event that occurs as the result of DoEvents restarts the entire method over again. The second approach, non-blocking asynchronous, is to use an event to trigger the resumption of the processing when the resource has loaded. For example,
When the method ends the thread can continue to process events and when the resource is loaded the assigned event handler is invoked and processing continues, The problem with this is that the event handler has to be written else where and its self contained and doesn't share access to any of the resources of the original processing method. That is the logic of the program says:
and A and B are linked in the sense that B might well need access to variables that were created as part of A but the need not to block the thread waiting for the resource load mean this has to be written as:
Not only is the flow of control confused by this break but B doesn't have access to the variables etc in the scope of A. At this point you should be thinking - closure. If the event handler is defined as an anonymous method in the scope of A then when it runs, whenever that might be it has access to the original environment provided by A. That is:
Now not only is it clear that the flow of control is do A then do B but B has access all of the variables defined in A and can continue the processing as if it hadn't had to wait for an event. If you would like to see a practical example of this in action then see Loading Bitmaps: DoEvents and the closure pattern. The use of closure to make non-blocking asynchronous coding look more like blocking synchronous coding is one of the big advantages of using a closure - but don't over use it! In modern C# a better way of handling events and asynchronous code is to use async and await. Deep C#Buy Now From Amazon Chapter List
Extra Material <ASIN:1871962714> <ASIN:B09FTLPTP9>
Comments
or email your comment to: comments@i-programmer.info <ASIN:0321658701> <ASIN:0321637003> <ASIN:0596800959> <ASIN:047043452X> <ASIN:0123745144>
|
||||||
Last Updated ( Thursday, 22 September 2016 ) |