Generic Delegates


As discussed in Chapter 7, “Delegates and Events,” delegates are type-safe references to methods. With generic delegates, the parameters of the delegate can be defined later.

.NET 2.0 defines a generic EventHandler delegate with the second parameter of type TEventArgs, so it is no longer necessary to define a new delegate with every new parameter type:

  public sealed delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)    where TEventArgs : EventArgs 

Implementing Methods Called by Delegates

The method Accumulate() is changed to have two generic types. TInput is the type of the objects that are accumulated, and TSummary is the returned type. The first parameter of Accumulate is the interface IEnumerable<T>, as it was before. The second parameter requires the Action delegate to reference a method that is invoked to accumulate all balances.

With the implementation, the method referenced by the Action delegate is now invoked for every element, and then the sum of the calculation is returned:

  public delegate TSummary Action<TInput, TSummary>(TInput t, TSummary u); public static TSummary Accumulate<TInput, TOutput>(IEnumerable<T> coll,       Action<TInput, TSummary> action) {    TSummary sum = default(TSummary);    foreach (TInput input in coll)    {       sum = action(input, sum);    }    return sum; } 

The method Accumulate can be invoked using an anonymous method that specifies that the balance of the account should be added to the second parameter:

  decimal amount = Accumulate<Account, decimal>(       accounts, delegate(Account a, decimal d) { return a.Balance + d; }); 

If the addition of Account balances is needed more than once, it is useful to move the functionality into a separate method, AccountAdder():

  static decimal AccountAdder(Account a, decimal d) {    return a.Balance + d; } 

And use the AccountAdder method with the Accumulate method:

  decimal amount = Accumulate<Account, decimal>(       accounts, AccountAdder); 

The method referenced by the Action delegate can implement any logic; for example, a multiplication could be done instead of a summation.

The Accumulate() method is made more flexible with the AccumulateIf() method. With AccumulateIf(), an additional parameter of type Predicate<T> is used. The delegate Predicate<T> references the method that will be invoked to check whether the account should be part of the accumulation. In the foreach statement, the action method will be invoked only if the predicate match returns true:

 public static TSummary AccumulateIf<TInput, TSummary>(       IEnumerable<TInput> coll,       Action<TInput, TSummary> action,       Predicate<TInput> match) {    TSummary sum = default(TSummary);    foreach (TInput a in coll)    {       if (match(a))       {          sum = action(a, sum);       }    }    return sum; }

Calling the method AccumulateIf() can have an implementation for the accumulation and an implementation for the predicate. Here, only the accounts with a balance higher than 2000 are accumulated:

  decimal amount = Algorithm.AccumulateIf<Account, decimal>(    accounts,    delegate(Account a, decimal d)    { return a.Balance + d; },    delegate(Account a)    { return a.Balance > 2000; } ); 

Using Generic Delegates with the Array Class

Chapter 5, “Arrays,” demonstrated different sort techniques with the Array class by using the IComparable and IComparer interfaces. Starting with .NET 2.0, some methods of the Array class use generic delegate types as parameters. The following table shows these methods, the generic type and the functionality.

Open table as spreadsheet

Method

Generic Parameter Type

Description

Sort()

int Comparison<T>(T x, T y)

The Sort() method defines several overloads. One overload requires a parameter of type Comparison<T>. Sort() is using the method referenced by the delegate for ordering all elements in the collection.

ForEach()

void Action<T>(T obj)

The method ForEach() invokes the method referenced by the Action<T> delegate with every item in the collection.

FindAll() Find() FindLast() FindIndex() FindLastIndex()

bool Predicate<T>(T match)

The FindXXX() methods accept the Predicate<T> delegate as parameter. The method referenced by the delegate is invoked multiple times, and the elements of the collection are passed one after the other. The Find() method stops a search until the predicate returned true the first time and returns this element. FindIndex() returns the index of the first element found. FindLast() and FindLastIndex() invoke the predicate in the reversed order of the elements in the collection, and thus either return the last item or the last index. FindAll() returns a new list with all items where the predicate was true.

ConvertAll()

TOutput Converter<TInput, TOutput>(TInput input)

The ConvertAll() method invokes the Converter<TInput, TOutput> delegate for every element in the collection and returns a list of converted elements.

TrueForAll()

bool Predicate<T>(T match)

The method TrueForAll() invokes the predicate delegate for every element. If the predicate returns true for every element, TrueForAll() returns true as well. If the predicate returns false just for one of the elements, TrueForAll() returns false.

Let’s get into how these methods can be used.

The Sort() method accepts this delegate as parameter:

  public delegate int Comparison<T>(T x, T y); 

This way, it is possible to sort the array by using an anonymous delegate passing two Person objects. With an array of Person objects, parameter T is of type Person:

  Person[] persons = {    new Person("Emerson", "Fittipaldi"),    new Person("Niki", "Lauda"),    new Person("Ayrton", "Senna"),    new Person("Michael", "Schumacher") }; Array.Sort(persons,    delegate(Person p1, Person p2)    {       return p1.Firstname.CompareTo(p2.Firstname);    }); 

The Array.ForEach() method accepts an Action<T> delegate as parameter to invoke the action for every element of the array:

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

This way, you can write every person to the console by passing the address of the method Console .WriteLine. One overload of the WriteLine() method accepts the Object class as parameter type. Because Person derives from Object, this fits with a Person array:

  Array.ForEach(persons, Console.WriteLine); 

The result of the ForEach() statement writes every person of the collection referenced by the persons variable to the console:

 Emerson Fittipaldi Niki Lauda Ayrton Senna Michael Schumacher

If more control is needed, you can pass an anonymous method that fits the parameter defined by the delegate:

  Array.ForEach(persons,    delegate (Person p)    {       Console.WriteLine("{0}", p.Lastname);    }); 

Here, the result is the last name written to the console:

 Fittipaldi Lauda Senna Schumacher

The Array.FindAll() method requires the Predicate<T> delegate:

  public delegate bool Predicate<T>(T match); 

The Array.FindAll() method invokes the predicate for every element in the array and returns a new array where the predicate returns true for the element. In the example, true is returned for all Person objects where the Lastname starts with the string “S”:

  Person[] sPersons = Array.FindAll(persons,    delegate (Person p)    {       return p.Lastname.StartsWith("S");    }); 

Iterating through the returned collection sPersons to write it to the console gives this result:

 Ayrton Senna Michael Schumacher

The Array.ConvertAll() method used the generic delegate Converter with two generic types. The first generic type TInput is the input parameter, the second generic type TOutput is the return type:

  public delegate TOutput Converter<TInput, TOutput>(TInput input); 

The ConvertAll() method is very useful if an array of one type should be converted to an array of another type. Following is a Racer class that is unrelated to the Person class. The Person class has a Firstname and a Lastname property, while the Racer class defines for the name of the racer just one property Name:

  public class Racer {    public Racer(string name)    {       this.name = name;    }    private string name;    public string Name    {       get { return name; }       set { name = value; }    }    private string team;    public string Team    {       get { return team; }       set { team = value; }    } } 

Using Array.ConvertAll() you can easily convert the person array persons to a Racer array. The delegate is invoked for every Person element. In the anonymous method implementation for every person, a new Racer object is created, and the firstname and lastname are passed concatenated to the constructor, which accepts a string. The result is an array of Racer objects:

  Racer[] racers =       Array.ConvertAll<Person, Racer>(             persons,             delegate(Person person)             {                return new Racer(person.Firstname + " " + person.Lastname);             }); 




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net