Chapter 16

Advanced Language Features

C# is such a powerful language with lots of great features. This chapter discusses some of the more important features of the language that are not discussed anywhere else in this book.

Delegates

The delegate has been part of the C# language since version 2.0 and without a doubt is the most difficult .NET type to grasp. Delegates are similar to function pointers in C and C++. Unlike C/C++ function pointers, however, delegates are type-safe. If you do not know C or C++, you might have to read this section a couple of times before you get a good understanding of delegates. I will start by explaining why you might need delegates, then talk about the definition of the term itself and present a couple of examples.

Suppose you want a method (Method1) to invoke another method (Method2) after the first method has finished executing. Even without using a delegate you can achieve this easily like so.

public void Method1()
{
    // do something
    ...
    // then call Method2
    Method2();
}

public void Method2()
{
    ...
}

This is easy because you control the source code of both Method1 and Method2. However, suppose Method1 is a method in the class library and of course you cannot change it. How do you make Method1 call Method2?

You would probably say, why is it such a big deal? In many cases, if you want a method to be invoked after another method, you can simply call both methods in your code. It is easy to do even if the two methods are implemented in different classes. For example, the following code snippet assumes that Method1 is part of Type1 and Method2 part of Type2. It is easy to make Method2 execute after Method1:

public void Main()
{
    Type1 object1 = new Type1();
    Type2 object2 = new Type2();
    object1.Method1();
    object2.Method2()
}

So, why the need for delegates at all?

The thing is there are cases whereby a method needs to be invoked not by your own code, but by another module or even by the system. For example, you might want to do something in response to a button click in a GUI application. In this case, you cannot use a conventional method because when the system was written, the system developers did not know ahead of time which method(s) to call when a button was clicked. However, they recognized that users of their system might want to perform some operation in response to a button click, and they facilitated that by creating a delegate type. To have your method called when a button is clicked, you create an instance of the delegate type that references your method. The system can then call your method through the delegate instance.

The button click example is one use of the delegate, i.e. as an event listener. Another use of the delegate is as a callback. A callback is a method that is passed as an argument to other code. The other code can then call back (execute) the passed-in method at some convenient time. The invocation may be immediate as in a synchronous callback, or it might happen at later time as in an asynchronous callback.

Conceptually, the delegate is a mechanism for allowing a method to be called by a caller even when the caller has no prior knowledge of the method it is about to call.

Technically, a delegate is a type that references a method. Declaring a delegate type is similar to declaring a method signature, you would need a name, a list of arguments and a return value. A delegate declaration is important since it determines the kind of method that the delegate can reference. The methods that can be referenced by a delegate instance must have the same signature as the delegate type itself.

To declare a delegate type, use the keyword delegate. Here is an example delegate. It is called MyDelegate, takes a string and does not return a value.

public delegate void MyDelegate(string s1)

You instantiate a delegate either by using the new operator or by assigning a method to a delegate variable. Therefore, these two lines of code are equivalent:

MyDelegate delegate1 = new MyDelegate(myMethod);
MyDelegate delegate1 = myMethod;

In this case, myMethod must be a method that has the same signature as the delegate type. Once you have a delegate instance, you can pass it to a caller.

Note

When discussing delegates, we are talking about two different things. The first is the delegate type, which defines the kind of method that can be referenced by instances of the delegate type. The second is the delegate instance, which is an object created out of the delegate type and references a method or methods.

A Delegate Example

The following example illustrates the use of a delegate as a callback. Listing 16.1 shows a class with a method for downloading a web page given an Internet address. It also declares a delegate called ResultHandler that the method will call after the method has finished executing.

Listing 16.1: The Downloader class and the ResultHandler delegate

namespace DelegateExample
{
    using System.Net;

    public delegate void ResultHandler(string text);

    public class Downloader
    {
        public void Download(string url, ResultHandler handler)
        {
            string content;
            using (WebClient webClient = new WebClient())
            {
                content = webClient.DownloadString(url);
            }
            handler(content);
        }
    }
}

The Download method in Listing 16.1 takes two arguments. The first argument is a URL and the second a delegate that it will call after it has retrieved the web page pointed to by the URL. The download operation itself is performed by the System.Net.WebClient class.

At the end of the Download method, the delegate instance is invoked, in the same way you would invoke a method.

handler(content);

To use the Downloader class, you need to pass a method with the same signature as the ResultHandler delegate type. Listing 16.2 shows code that uses it.

Listing 16.2: Using the ResultHandler delegate

using System;
using DelegateExample;

namespace AdvancedLanguageFeatures
{
    class Program
    {
        static void CountWords(string html)
        {
            Console.WriteLine("The page contains {0} words", 
                    html.Split(' ').Length);
        }

        static void Main(string[] args)
        {
            Downloader downloader = new Downloader();
            ResultHandler myDelegate = new ResultHandler(CountWords);
            downloader.Download("http://news.yahoo.com", myDelegate);
        }
    }
}

The code in Listing 16.2 creates an instance of Downloader and calls its Download method, passing a delegate type referencing CountWords. CountWords parses the web page content and uses the String class’s Split method to count the number of words.

You can replace the following line of code

ResultHandler myDelegate = new ResultHandler(CountWords);

with

ResultHandler myDelegate = CountWords;

You can even pass CountWords to the Download method without explicitly creating a delegate instance:

downloader.Download("http://news.yahoo.com", CountWords);

Multicasting

A delegate instance may reference more than one method. When the delegate instance is invoked, all the methods it references are executed. This is called multicasting.

Take this PrettyDelegate delegate type as an example:

public delegate void PrettyDelegate(string s);

Suppose you have three instances of PrettyDelegate that reference three different methods:

PrettyDelegate delegate1 = obj.Method1;
PrettyDelegate delegate2 = obj.Method2;
PrettyDelegate delegate3 = StaticMethod1;

To create a delegate instance that references all the three methods, you would do this:

PrettyDelegate ultimate = delegate1 + delegate2;
ultimate += delegate3;

To remove a method, use the -= operator. For example, the following line of code removes obj.Method1.

ultimate -= delegate1;

Predefined Delegates

The System namespace contains a number of predefined delegates that can be used for the most common cases. Some of these general-purpose delegates are listed in Table 16.1.

Delegate

Description

Action

References a method that does not return a value and takes zero, one or multiple arguments. There are seventeen versions of this delegate, taking zero, one to sixteen arguments.

AsyncCallback

References a method that will be called when a corresponding asynchronous operation completes.

Comparison

References a method that compares two objects of the same type.

Converter

References a method that converts an object from one type to another type.

EventHandler

References a method that handles an event that has no event data.

Func

References a method that returns a value and takes zero, one or multiple parameters. There are seventeen versions of this delegate, taking zero, one to sixteen parameters.

Predicate

References a method that defines a set of criteria and determines whether the specified object meets those criteria.

Table 16.1: Predefined delegates

For example, this variant of the Action delegate type returns void and accepts one argument.

public delegate void Action<in T>(T obj)

If you happen to need a delegate that takes one argument and returns void, you can use Action instead of creating your own. The ActionDownloader class in Listing 16.3 is a rewrite of the Downloader class in Listing 16.1. Note that the Download method in ActionDownloader accepts an Action delegate instance, instead of an instance of a home-made delegate.

Listing 16.3: The ActionDownloader class

using System;
using System.Net;

namespace ActionDelegate
{
    public class ActionDownloader
    {
        public void Download(string url, Action<string> handler)
        {
            string content;
            using (WebClient webClient = new WebClient())
            {
                content = webClient.DownloadString(url);
            }
            handler(content);
        }
    }
}

Listing 16.4 shows how to use the ActionDownloader class.

Listing 16.4: Using the ActionDownloader class

using System;
using ActionDelegate;
namespace AdvancedLanguageFeatures
{
    class Program
    {
        static void CountWords(string html)
        {
            Console.WriteLine("The page contains {0} words", 
                    html.Split(' ').Length);
        }

        static void Main(string[] args)
        {
            ActionDownloader downloader2 = new ActionDownloader();
            downloader2.Download("http://news.yahoo.com", CountWords);
        }
    }
}

Events

Events are related to delegates. As such, you need to understand delegates before you can tackle events.

In the two examples in the section above, the method that you passed to the Download method through a delegate instance got invoked shortly after you had invoked the Download method. In certain cases, notably in a GUI environment, you do not want your method to be called right away. You’d rather wait until an event occurs before it is called. For example, if your method does an interesting operation in response to a button click, you want to your method to execute right after the user clicks the button and not before that.

In cases like this, you need the caller object (the object that will eventually call your method) to take your delegate instance and keep it. Only when an event occurs will your method be called. In order for an object to keep a delegate instance, its class must declare an event member. The type of an event member must be a delegate type.

Here is a class that declares an event called DoubleClick of type EventHandler, which is is a delegate type in the System namespace. (See Table 16.1.)

public MyClass
{
    public event EventHandler DoubleClick;
    ...
}

Basically, an event is a class member like a field or a property. Just like a field can hold a value, an event can hold a delegate instance.

You can pass a delegate instance to the DoubleClick event using the += operator:

MyClass obj = new MyClass():
obj.MyEventHandler += delegate;

Listing 16.5 shows a more detailed example that features a SmartStorage class. Admittedly, the example is a little contrived, but it shows how to create an event.

SmartStorage contains a queue that can be accessed only through its Put and Take methods. You call Put to add a new element and Take to pop an element in the queue. The capacity of the queue is 10 and it will not grow more than that. Every time the Put method is called when there are already six or more elements in the queue, an instance of SmartStorage will raise a CapacityEvent event.

Listing 16.5: An event example

using System;
using System.Collections.Generic;
namespace EventExample
{
    class SmartStorage<T>
    {
        private int maxSize = 10;
        private Queue<T> queue = new Queue<T>();
        public event EventHandler CapacityEvent;

        public void Put(T element)
        {
            if (queue.Count < maxSize)
            {
                queue.Enqueue(element);
            }
            if (queue.Count > 7)
            {
                if (CapacityEvent != null)
                {
                    CapacityEvent(this, EventArgs.Empty);
                }
            }
        }

        public T Take()
        {
            if (queue.Count > 0)
            {
                return queue.Dequeue();
            }
            return default(T);
        }
    }
}

The most important part of SmartStorage is the event declaration:

public event EventHandler CapacityEvent;

EventHandler is a delegate type and CapacityEvent is the name for the event.

The code in Listing 16.6 shows how you can use SmartStorage and pass to it a delegate that acts as an event listener (event handler).


Listing 16.6: Using the custom event

using System;
using EventExample;
namespace AdvancedLanguageFeatures
{
    class Program
    {
        static void Main(string[] args)
        {
            SmartStorage<int> storage = new SmartStorage<int>();
            storage.CapacityEvent += 
                    new EventHandler(HandleCapacityEvent);
            for (int i = 0; i < 10; i++)
            {
                storage.Put(i);
            }
            Console.ReadKey();
        }

        static void HandleCapacityEvent(Object sender, 
                EventArgs eventArgs)
        {
            Console.WriteLine("Storage almost full.");
        }
    }
}

In this case, you pass the HandleCapacityEvent method in the code as an event handler.

storage.CapacityEvent += new EventHandler(HandleCapacityEvent);

The method will be invoked whenever the SmartStorage instance raises a CapacityEvent event. The for loop is used to illustrate the point. You should see this in your console when you run the example:

Storage almost full.
Storage almost full.
Storage almost full.

You will learn more about events in Chapter 18, “Windows Presentation Foundation.”

Anonymous Methods

If a method is only used once during an application’s life time, you can create an anonymous method. Introduced in C# 2.0, anonymous methods do not require a name and are declared inline using the delegate keyword.

For instance, the code in Listing 16.7 uses an anonymous method to handle the CapacityEvent event of an instance of the SmartStorage class in Listing 16.5.

Listing 16.7: Using an anonymous method

using System;
using EventExample;
namespace AdvancedLanguageFeatures
{
    class Program
    {
        static void Main(string[] args)
        {
            SmartStorage<int> storage2 = new SmartStorage<int>();
            storage2.CapacityEvent += delegate(object o, EventArgs e)
            {
                Console.WriteLine("Storage at more than 69% capacity");
            }; // ended with semicolon
            for (int i = 0; i < 10; i++)
            {
                storage2.Put(i);
            }
        }
    }
}

Note that there is a semicolon at the end of the method declaration.

As of C# 3.0, lambda expressions are preferred over anonymous methods as a way to write inline code.

Lambda Expressions

Lambda expressions are anonymous methods. You can use them to create, among others, delegate types. Lambda expressions are used extensively in LINQ, which you learn in Chapter 17, “LINQ.”

The syntax for lambdas are one of these:

(input parameters) => expression
(input parameters) => { statement(s); }

The brackets are optional if there is at least one input parameter.

Here is a lambda expression that adds two parameters and returns the result.

x, y => x + y;

In most cases, the compiler is smart enough to infer the types of the parameters. In the event you need to provide it with types, here is how you do it:

int x, int y => x + y;

Listing 16.8 shows how to use a lambda to handle the CapacityEvent in SmartStorage in Listing 16.5..

Listing 16.8: Using a lambda as an event handler

SmartStorage<int> storage3 = new SmartStorage<int>();
storage3.CapacityEvent += 
        (obj, eventArgs) => 
                { Console.WriteLine("Storage capacity warning."); };

for (int i = 0; i < 10; i++)
{
    storage3.Put(i);
}

Expression-Bodied Members

Starting from C# 6.0, you can write a method or read-only property with a lambda expression instead of a statement body.

For instance, here is a method called Add written in the conventional way.

public int Add(int a, int b)
{
    return a + b;
}

And, here is the same method written with a lambda expression.

public int Add(int a, int b) => a + b;

As another example, consider the following read-only property:

public string FullName
{
    get
    {
        return FirstName + " " + LastName;
    }
}

In C# 6.0 and later, the same expression can be written with a lambda expression as follows.

public string FullName => FirstName + " " + LastName;

Extension Methods

C# 3.0 added a new feature to allow you to add functionality to a type without the need to extend the type or modify it. You do this by creating a extension method, which is a special kind of static method. Unlike static methods, however, extension methods are called as if they were instance methods.

Listing 16.9 shows a class called MyExtension that houses an extension method for the String class. The extension method IgnoreCaseEquals can then be used on any instance of String as long as you import the StringExtension namespace.

Listing 16.9: Extending System.String

using System;
namespace StringExtension
{
    public static class MyExtension
    {
        public static bool IgnoreCaseEquals(this string s1, string s2)
        {
            return s1.Equals(s2, StringComparison.OrdinalIgnoreCase);
        }
    }
}

An extension method must be declared as a static method in a non-generic non-nested static class. The first parameter to the method specifies which type the method operates on and is preceded by the this modifier.

To use it, use using to import the namespace and call the method as if it was an instance method. You also ignore the first argument as if it did not exist:

string a = "abcd";
string b = "ABCD";
bool equals = a.IgnoreCaseEquals(b); // evaluates to true

The most common extension methods are the LINQ standard query operators that add query functionality to the existing System.Collections.IEnumerable and System.Collections.Generic.IEnumerable<T> types. You will learn LINQ in Chapter 17, “LINQ.”

Implicitly Typed Local Variables

C# is a statically typed language, which means variable types are checked at compile time. For example, the compiler will mark this code as erroneous because you are trying to assign a string to an int.

int word = "Hello!!!";

However, starting from C# 3.0 you can use the type var for local variables and have the compiler infer (derive) their types from the expressions on the right side. For example, the following code is valid:

var greeting = "Bonjour";

At compile time, greeting will have the type string.

Here is another example of var:

var numbers = { 0, 1, 2 };
foreach (var a in numbers)
{
    Console.WriteLine(a);
}

In the majority of cases, var is used as a shortcut for the actual type. In other words, as a syntactic convenience. However, var is also used with anonymous types, as explained in the following section.

Anonymous Types

Added in C# 3.0, the anonymous type lets you create an object without first defining a type. An anonymous type can be used to encapsulate read-only properties into an object that only needs to be in existence for a relatively short time.

You create an anonymous type by using the new operator and object initializers. You can assign the object a var variable. Here is an example:

var person = new { Name = "Joe Fresher", Age = 58 };
Console.WriteLine(person.Name);

The dynamic Type

I mentioned earlier that C# is a statically-type language, which means type checking occurs at compile-time. However, C# 4.0 added the dynamic type to let you bypass the compiler’s type-checking and defer it to runtime.

The purpose of using the dynamic type is to make it easier to program objects or data whose types are not known at compile-time, such as when using reflection or when retrieving values from a COM API.

Here is an example of using dynamic:

dynamic someObject = getObject();
someObject.someMethod(1, 2, 4);

The compiler will not check the type of someObject. Nor will it verify if someMethod exists.

Summary

C# is a feature rich language. Every new version of the language brings with it new features that make the language even richer.

The delegate is an especially useful and important feature, used frequently as a callback or event listener. You can create a delegate instance by using the new operator or by simply passing a matching method to a delegate type variable. Alternatively, you can use a lambda to create a delegate instance because a lambda is essentially an anonymous method.

Lambdas are interesting as they bring functional programming to C#. LINQ technology, which you learn in Chapter 17, “LINQ” uses a lot of lambdas.

Another important feature is the extension method, which allows you to add functionality to an existing class without creating a subclass or modifying the class itself.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.214.230