Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   C#’s Syntactical Sugar for Delegates

Most programmers find working with delegates to be cumbersome because the syntax is so strange.

For example, take this line of code.

 

button1.Click += new EventHandler(button1_Click);

 

where button1_Click is a method that looks something like this.

 

void button1_Click(Object sender, EventArgs e) {

// Do something, the button was clicked...

}

 

The idea behind the first line of code is to register the address of the button1_Click method with a button control so that when the button is clicked, the method will be called. To most pro- grammers, it feels quite unnatural to construct an EventHandler delegate object just to specify the


address of the button1_Click method. However, constructing the EventHandler delegate object is required for the CLR because this object provides a wrapper that ensures that the method can be called only in a type-safe fashion. The wrapper also allows the calling of instance methods and chain- ing. Unfortunately, most programmers don’t want to think about these details. Programmers would prefer to write the preceding code as follows.

 

button1.Click += button1_Click;

 

Fortunately, Microsoft’s C# compiler offers programmers some syntax shortcuts when working with delegates. I’ll explain all of these shortcuts in this section. One last point before we begin: what I’m about to describe really boils down to C# syntactical sugar; these new syntax shortcuts are really just giving programmers an easier way to produce the IL that must be generated so that the CLR and other programming languages can work with delegates. This also means that what I’m about to describe is specific to C#; other compilers might not offer the additional delegate syntax shortcuts.

 

 

Syntactical Shortcut #1: No Need to Construct a Delegate Object

As demonstrated already, C# allows you to specify the name of a callback method without having to construct a delegate object wrapper. Here is another example.

 

internal sealed class AClass {

public static void CallbackWithoutNewingADelegateObject() { ThreadPool.QueueUserWorkItem(SomeAsyncTask, 5);

}

 

private static void SomeAsyncTask(Object o) { Console.WriteLine(o);

}

}

 

Here, the ThreadPool class’s static QueueUserWorkItem method expects a reference to a Wait­ Callback delegate object that contains a reference to the SomeAsyncTask method. Because the C# compiler is capable of inferring this on its own, it allows me to omit code that constructs the Wait­ Callback delegate object, making the code much more readable and understandable. Of course, when the code is compiled, the C# compiler does produce IL that does, in fact, new up the Wait­ Callback delegate object—we just got a syntactical shortcut.


Syntactical Shortcut #2: No Need to Define a Callback Method

(Lambda Expressions)

In the preceding code, the name of the callback method, SomeAsyncTask, is passed to the Thread­ Pool’s QueueUserWorkItem method. C# allows you to write the code for the callback method inline so it doesn’t have to be written inside its very own method. For example, the preceding code could be rewritten as follows.



 

internal sealed class AClass {

public static void CallbackWithoutNewingADelegateObject() { ThreadPool.QueueUserWorkItem( obj => Console.WriteLine(obj), 5);

}

}

 

Notice that the first “argument” to the QueueUserWorkItem method is code (which I italicized)! More formally, the italicized code is called a C# lambda expression, and it is easy to detect due to the use of C#'s lambda expression operator: =>. You may use a lambda expression in your code where the compiler would normally expect to see a delegate. And, when the compiler sees the use of this lambda expression, the compiler automatically defines a new private method in the class (AClass,

in this example). This new method is called an anonymous function because the compiler creates the name of the method for you automatically, and normally, you wouldn’t know its name. However, you could use a tool such as ILDasm.exe to examine the compiler-generated code. After I wrote the

preceding code and compiled it, I was able to see, by using ILDasm.exe, that the C# compiler decided to name this method <CallbackWithoutNewingADelegateObject>b 0 and ensured that this method took a single Object argument and returned void.

The compiler chose to start the method name with a < sign because in C#, an identifier cannot contain a < sign; this ensures that you will not accidentally define a method that coincides with the name the compiler has chosen for you. Incidentally, while C# forbids identifiers to contain a < sign, the CLR allows it, and that is why this works. Also, note that although you could access the method via reflection by passing the method name as a string, the C# language specification states that there is no guarantee of how the compiler generates the name. For example, each time you compile the code, the compiler could produce a different name for the method.

Using ILDasm.exe, you might also notice that the C# compiler applies the System.Runtime.

CompilerServices.CompilerGeneratedAttribute attribute to this method to indicate to vari- ous tools and utilities that this method was produced by a compiler as opposed to a programmer. The code to the right of the => operator is then placed in this compiler-generated method.

       
   
 
 


Finally, if you write the preceding code and compile it, it’s as if the C# compiler rewrote your code to look like the following (comments inserted by me).

 

internal sealed class AClass {

// This private field is created to cache the delegate object.

// Pro: CallbackWithoutNewingADelegateObject will not create

// a new object each time it is called.

// Con: The cached object never gets garbage collected [CompilerGenerated]

private static WaitCallback <>9 CachedAnonymousMethodDelegate1;

 

public static void CallbackWithoutNewingADelegateObject() { if (<>9 CachedAnonymousMethodDelegate1 == null) {

// First time called, create the delegate object and cache it.

<>9 CachedAnonymousMethodDelegate1 =

new WaitCallback(<CallbackWithoutNewingADelegateObject>b 0);

}

ThreadPool.QueueUserWorkItem(<>9 CachedAnonymousMethodDelegate1, 5);

}

 

[CompilerGenerated]

private static void <CallbackWithoutNewingADelegateObject>b 0(Object obj) { Console.WriteLine(obj);

}

}

 

The lambda expression must match that of the WaitCallback delegate: it returns void and takes an Object parameter. However, I specified the name of the parameter by simply putting obj to the left of the => operator. On the right of the => operator, Console.WriteLine happens to return void. However, if I had placed an expression that did not return void, the compiler-generated code would just ignore the return value because the method that the compiler generates must have a void return type to satisfy the WaitCallback delegate.

It is also worth noting that the anonymous function is marked as private; this forbids any code not defined within the type from accessing the method (although reflection will reveal that the meth- od does exist). Also, note that the anonymous method is marked as static; this is because the code doesn’t access any instance members (which it can’t because CallbackWithoutNewingADelegate­ Object is itself a static method. However, the code can reference any static fields or static methods defined within the class. Here is an example.

 

internal sealed class AClass {

private static String sm_name; // A static field

 

public static void CallbackWithoutNewingADelegateObject() { ThreadPool.QueueUserWorkItem(

// The callback code can reference static members. obj =>Console.WriteLine(sm_name + ": " + obj),

5);

}

}


If the CallbackWithoutNewingADelegateObject method had not been static, the anonymous method’s code could contain references to instance members. If it doesn’t contain references to instance members, the compiler will still produce a static anonymous method because this is more efficient than an instance method because the additional this parameter is not necessary. But, if the anonymous method’s code does reference an instance member, the compiler will produce a nonstatic anonymous method.

 

internal sealed class AClass {

private String m_name; // An instance field

 

// An instance method

public void CallbackWithoutNewingADelegateObject() { ThreadPool.QueueUserWorkItem(

// The callback code can reference instance members. obj => Console.WriteLine(m_name + ": " + obj),

5);

}

}

 

On the left side of the => operator is where you specify the names of any arguments that are to be passed to the lambda expression. There are some rules you must follow here. See the following examples.

 

// If the delegate takes no arguments, use () Func<String> f = () => "Jeff";

 

// If the delegate takes 1+ arguments, you can explicitly specify the types Func<Int32, String> f2 = (Int32 n) => n.ToString();

Func<Int32, Int32, String> f3 = (Int32 n1, Int32 n2) => (n1 + n2).ToString();

 

// If the delegate takes 1+ arguments, the compiler can infer the types Func<Int32, String> f4 = (n) => n.ToString();

Func<Int32, Int32, String> f5 = (n1, n2) => (n1 + n2).ToString();

 

// If the delegate takes 1 argument, you can omit the ()s Func<Int32, String> f6 = n => n.ToString();

 

// If the delegate has ref/out arguments, you must explicitly specify ref/out and the type Bar b = (out Int32 n) => n = 5;

 

For the last example, assume that Bar is defined as follows.

 

delegate void Bar(out Int32 z);

 

On the right side of the => operator is where you specify the anonymous function body. It is very common for the body to consist of a simple or complex expression that ultimately returns a non- void value. In the preceding code, I was assigning lambda expressions that returned Strings to all the Func delegate variables. It is also quite common for the body to consist of a single statement. An example of this is when I called ThreadPool.QueueUserWorkItem, passing it a lambda expression that called Console.WriteLine (which returns void).


If you want the body to consist of two or more statements, then you must enclose it in curly braces. And if the delegate expects a return value, then you must have a return statement inside the body. Here is an example.

 

Func<Int32, Int32, String> f7 = (n1, n2) => { Int32 sum = n1 + n2; return sum.ToString(); };

       
   
 
 

 

 

Syntactical Shortcut #3: No Need to Wrap Local Variables in a Class Manually to Pass Them to a Callback Method

I’ve already shown how the callback code can reference other members defined in the class. However, sometimes, you might like the callback code to reference local parameters or variables that exist in the defining method. Here’s an interesting example.

 

internal sealed class AClass {

public static void UsingLocalVariablesInTheCallbackCode(Int32 numToDo) {

// Some local variables

Int32[] squares = new Int32[numToDo]; AutoResetEvent done = new AutoResetEvent(false);

 

// Do a bunch of tasks on other threads

for (Int32 n = 0; n < squares.Length; n++) { ThreadPool.QueueUserWorkItem(

obj => {

Int32 num = (Int32) obj;


// This task would normally be more time consuming squares[num] = num * num;

 

// If last task, let main thread continue running if (Interlocked.Decrement(ref numToDo) == 0)

done.Set();

},

n);

}

 

// Wait for all the other threads to finish done.WaitOne();

 

// Show the results

for (Int32 n = 0; n < squares.Length; n++) Console.WriteLine("Index {0}, Square={1}", n, squares[n]);

}

}

 

This example really shows off how easy C# makes implementing what used to be a pretty complex task. The preceding method defines one parameter, numToDo, and two local variables, squares and done. And the body of the lambda expression refers to these variables.

Now imagine that the code in the body of the lambda expression is placed in a separate method (as is required by the CLR). How would the values of the variables be passed to the separate method? The only way to do this is to define a new helper class that also defines a field for each value that you want passed to the callback code. In addition, the callback code would have to be defined as an

instance method in this helper class. Then, the UsingLocalVariablesInTheCallbackCode method would have to construct an instance of the helper class, initialize the fields from the values in its local variables, and then construct the delegate object bound to the helper object/instance method.

       
   
 
 

 

This is very tedious and error-prone work, and, of course, the C# compiler does all this for you au- tomatically. When you write the preceding code, it’s as if the C# compiler rewrites your code so that it looks something like the following (comments inserted by me).

 

internal sealed class AClass {

public static void UsingLocalVariablesInTheCallbackCode(Int32 numToDo) {

 

// Some local variables WaitCallback callback1 = null;


// Construct an instance of the helper class

<>c DisplayClass2 class1 = new <>c DisplayClass2();

 

// Initialize the helper class's fields class1.numToDo = numToDo;

class1.squares = new Int32[class1.numToDo]; class1.done = new AutoResetEvent(false);

 

// Do a bunch of tasks on other threads

for (Int32 n = 0; n < class1.squares.Length; n++) { if (callback1 == null) {

// New up delegate object bound to the helper object and

// its anonymous instance method callback1 = new WaitCallback(

class1.<UsingLocalVariablesInTheCallbackCode>b 0);

}

 

ThreadPool.QueueUserWorkItem(callback1, n);

}

 

// Wait for all the other threads to finish class1.done.WaitOne();

 

// Show the results

for (Int32 n = 0; n < class1.squares.Length; n++) Console.WriteLine("Index {0}, Square={1}", n, class1.squares[n]);

}

 

// The helper class is given a strange name to avoid potential

// conflicts and is private to forbid access from outside AClass [CompilerGenerated]

private sealed class <>c DisplayClass2 : Object {

 

// One public field per local variable used in the callback code public Int32[] squares;

public Int32 numToDo; public AutoResetEvent done;

 

// public parameterless constructor public <>c DisplayClass2 { }

 

// Public instance method containing the callback code

public void <UsingLocalVariablesInTheCallbackCode>b 0(Object obj) { Int32 num = (Int32) obj;

squares[num] = num * num;

if (Interlocked.Decrement(ref numToDo) == 0) done.Set();

}

}

}


 

ImportantWithout a doubt, it doesn’t take much for programmers to start abusing C#’s lambda expression feature. When I first started using lambda expressions, it definitely took me some time to get used to them. After all, the code that you write in a method is not ac- tually inside that method, and this also can make debugging and single-stepping through the code a bit more challenging. In fact, I’m amazed at how well the Microsoft Visual Studio debugger actually handles stepping through lambda expressions in my source code.

I’ve set up a rule for myself: If I need my callback method to contain more than three lines of code, I will not use a lambda expression; instead, I’ll write the method manually and as- sign it a name of my own creation. But, used judiciously, lambda expressions can greatly increase programmer productivity as well as the maintainability of your code. The follow- ing is some code in which using lambda expressions feels very natural. Without them, this code would be tedious to write, harder to read, and harder to maintain.

// Create and initialize a String array

String[] names = { "Jeff", "Kristin", "Aidan", "Grant" };

 

// Get just the names that have a lowercase 'a' in them. Char charToFind = 'a';

names = Array.FindAll(names, name => name.IndexOf(charToFind) >= 0);

 

// Convert each string's characters to uppercase

names = Array.ConvertAll(names, name => name.ToUpper());

 

// Display the results Array.ForEach(names, Console.WriteLine);

 


Date: 2016-03-03; view: 733


<== previous page | next page ==>
Enough with the Delegate Definitions Already | Nbsp;   Delegates and Reflection
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.014 sec.)