天天看点

Introduce to C# anonymous methods

URL:http://www.theserverside.net/tt/articles/showarticle.tss?id=AnonymousMethods

Introduction

Generics are often introduced as the mainstream language feature of Whidbey (.NET2). However, while surfing off the beaten path I realized that new features named anonymous methods and iterators are also very interesting. Unlike generics, these two features don't imply new IL instructions set changes compared to .NET1 IL instructions set or any CTS changes. All the magic is in the compilers. This article is the first of a series of two articles dedicated to these new features. While these articles unfold, you will understand the reasons for beginning with anonymous methods. Both articles share the same structure: first a basic introduction to the functionality, followed by a strong analysis of the compiler work before drilling into advanced uses.

<script type="text/javascript"> if ( renderDoubleClickAd != null && typeof renderDoubleClickAd == "function" ) renderDoubleClickAd(); </script>

Introduce to C# anonymous methods
width="300" scrolling="no" height="250" frame marginwidth="0" marginheight="0" src="http://ad.doubleclick.net/adi/atssn/tt;bkg=FFFFFF;kw=;dcopt=;pos=1;sz=300x250;ptile=1;ord=8192936?"> &amp;lt;a href='http://ad.doubleclick.net/jump/atssn/tt;bkg=FFFFFF;kw=;dcopt=;pos=1;sz=300x250;ptile=1;ord=8192936?''&amp;gt; &amp;lt;img src='http://ad.doubleclick.net/ad/atssn/tt;bkg=FFFFFF;kw=;dcopt=;pos=1;sz=300x250;ptile=1;ord=8192936?'' width='300' height='250' &amp;gt;&amp;lt;/a&amp;gt;

Introduction to anonymous methods in C# v2

A simple example

Let's begin by enhancing some C# v1 code to use C# v2 anonymous methods. Here is a simple C# v1 program that first references and then invokes a method, through a delegate.

using System;
class Program{
   delegate void DelegateType();
   static DelegateType GetMethod(){
      return new DelegateType(MethodBody);
   }
   static void MethodBody(){
       Console.WriteLine("Hello");
   }
   static void Main(){
      DelegateType delegateInstance = GetMethod();
      delegateInstance();
      delegateInstance();
      Console.ReadKey();
   }
}
      

Here is the same program rewritten with a C# v2 anonymous method:

using System;
class Program{
   delegate void DelegateType();
   static DelegateType GetMethod(){
      return delegate(){ Console.WriteLine("Hello");};
   }
   static void Main(){
      DelegateType delegateInstance = GetMethod();
      delegateInstance();
      delegateInstance();
      Console.ReadKey();
   }
}
      

You should notice that:

  • The delegate keyword has a new use in C# v2. When the C# v2 compiler found the delegate keyword inside a method body, it expects that it is followed by an anonymous method body.
  • It is possible to assign an anonymous method to a delegate reference
  • We understand why this feature is named anonymous method: the method defined in the body of GetMethod() is not named. Nevertheless, it is possible to invoke it because it is referenced by a delegate instance.

You should notice as well that it is possible to use the operator += to allow a delegate instance to reference several methods (anonymous or not):

using System;
class Program{
   delegate void DelegateType();
   static void Main(){
      DelegateType delegateInstance = delegate() {  Console.WriteLine("Hello"); };
      delegateInstance += delegate() {  Console.WriteLine("Bonjours"); };
      delegateInstance();
      Console.ReadKey();
   }
}
      

As you might expect this program outputs:

Hello
Bonjours
      

Anonymous methods can accept some arguments

As shown in the following example, an anonymous method can accept some arguments of any type. You can also use keywords ref and out to tune how arguments are passed:

using System;
class Program{
   delegate int DelegateType(int valTypeParam, string refTypeParam, ref int refParam, out int outParam);
   static DelegateType GetMethod(){
      return delegate(int valTypeParam, string refTypeParam,ref int refParam,out int outParam)
      {
          Console.WriteLine("Hello valParam:{0} refTypeParam:{1}", valTypeParam, refTypeParam);
         refParam++;
         outParam = 9;
         return valTypeParam;
      };
   }
   static void Main(){
      delegateType delegateInstance = GetMethod();
      int refVar = 5;
      int outVar;
      int i = delegateInstance(1, "one", ref refVar, out outVar);
      int j = delegateInstance(2, "two", ref refVar, out outVar);
       Console.WriteLine("i:{0} j:{1} refVar:{2} outVar:{3}", i, j, refVar, outVar);
       Console.ReadKey();
   }
}
      

This program outputs:

Hello valParam:1 refTypeParam:one
Hello valParam:2 refTypeParam:two
i:1 j:2 refVar:7 outVar:9
      

As you can see, the returned type is not defined inside the anonymous method declaration. The returned type of an anonymous method is inferred by the C# v2 compiler from the returned type of the delegate to which it is assigned. This type is always known because the compiler forces assignment of any anonymous method to a delegate.

An anonymous method can't be tagged with an attribute. This restriction implies that you can't use the param keyword in the list of arguments of an anonymous method. Indeed, using the keyword param forces the compiler to tag the concerned method with the ParamArray attribute.

using System;
class Program{
   delegate void DelegateType(params int[] arr);
   static DelegateType GetMethod(){
      return delegate(params int[] arr){ // error : param is not valid in this context
          Console.WriteLine("Hello");
      };
   }
//...
}
      

A syntax subtlety

It is possible to declare an anonymous method without any signature, i.e. you are not compelled to write a pair of parenthesis after the keyword delegate if your anonymous method doesn't take any argument. In this case, your method can be assigned to any delegate instance that returns a void type and that doesn't have an out argument. Obviously, such an anonymous method doesn't have access to parameters that are provided through its delegate invocation.

using System;
class Program{
   delegate void DelegateType(int valTypeParam, string refTypeParam, ref int refParam);
   static void Main(){
      delegateType delegateInstance = delegate{ Console.WriteLine("Hello");};
      int refVar = 5;
      delegateInstance(1, "one", ref refVar);
      delegateInstance(2, "two", ref refVar);
       Console.ReadKey();
   }
}
      

Anonymous methods and generics

As shown in the example below, an argument of an anonymous method can have a generic type:

using System;
class UneClasse<T>{
   delegate void DelegateType(T t);
   internal void UneMethode(T t){
      delegateType delegateInstance = delegate(T arg){ Console.WriteLine("Hello arg:{0}",arg.ToString());};
      delegateInstance(t);
   }
}

class Program{
   static void Main(){
      UneClasse<double> inst = new UneClasse<double>();
      inst.UneMethode(5.5);
      Console.ReadKey();
   }
}
      

In.NET v2, a delegate type can be declared with some generics arguments. An anonymous method can be assigned to a delegate instance of such a type. You just have to resolve type parameters on both side of the assignment:

using System;
class Program{
   delegate void DelegateType<T>(T t);
   static void Main(){
      DelegateType<double> delegateInstance = delegate(double arg) {  Console.WriteLine("Hello arg:{0}", arg.ToString()); };
      delegateInstance(5.5);
      Console.ReadKey();
   }
}
      

Use of anonymous methods in the real world

Anonymous methods are particularly suited to define ‘small' methods that must be invoked ‘by design' through a delegate. For instance, you might use an anonymous method to code the entry point procedure of a thread:

using System;
using System.Threading;
class Program{
   static void Main(){
      Thread thread = new Thread(delegate(){
          Console.WriteLine("ThreadHashCode:{0} Hello",Thread.CurrentThread.GetHashCode());
      });
      thread.Start();
       Console.WriteLine("ThreadHashCode:{0} Bonjour", Thread.CurrentThread.GetHashCode());
       Console.ReadKey();
   }
}
      

This program outputs:

ThreadHashCode:1 Bonjour
ThreadHashCode:3 Hello
      

Another classic example of this kind of use lies in the winform's controls callback procedures:

public class UneForm : Form{
   Button m_Button;
   public UneForm(){
      InitializeComponent();
      m_Button.Click += delegate(object sender, EventArgs args){
         MessageBox.Show("m_Button Clicked");
      };
   }
   void InitializeComponent()
   {...}
}
      

It seems that anonymous method looks like a tiny language enhancement. It's now time to dig under the hood to realize that anonymous methods are far more complex and can be far more useful.

The C# v2 compiler and anonymous methods

The easy way

As you might expect, when an anonymous method is compiled, an extra method is spawned by the compiler in the concerned class.

using System;
class Program{
   delegate void DelegateType();
   static void Main(){
      DelegateType delegateInstance = delegate() {  Console.WriteLine("Hello"); };
      delegateInstance();
      Console.ReadKey();
   }
}
      

Thus, the following assembly is the compiled version of the previous program (the assembly is viewed with the tool Reflector provided freely by Lutz Roeder. Reflector supports right now .NET2 assemblies). :

Introduce to C# anonymous methods

Indeed, a new private and static method named <Main>b__0() has been automatically generated. We check that this method has the body of our anonymous method. If our anonymous method was declared inside an instance method, the generated method would have been an instance method.

We also note that a delegate field named <>9_CachedAnonymousMethoddelegate1 of type delegateType has been generated to reference our anonymous method.

It is interesting to note that all these generated members can't be viewed with the C# intellisense because their names contain a pair of angle brackets < >. Such names are valid for the CLR syntax but incorrect for the C# syntax.

Captured local variables

To keep things clear and simple, we haven't mentioned yet the fact that an anonymous method can have access to a local variable of its outer method. Let's analyze this possibility through the following example:

using System;
class Program{
   delegate int DelegateTypeCounter();
   static DelegateTypeCounter MakeCounter(){
      int counter = 0;
      DelegateTypeCounter delegateInstanceCounter = delegate { return ++counter; };
      return delegateInstanceCounter;
   }
   static void Main(){
      delegateTypeCounter counter1 = MakeCounter();
      delegateTypeCounter counter2 = MakeCounter();
       Console.WriteLine(counter1());
       Console.WriteLine(counter1());
       Console.WriteLine(counter2());
       Console.WriteLine(counter2());
       Console.ReadLine();
   }
}
      

This program outputs:

1
2
1
2
      

Think about it, it might stump you:

  • The local variable counter seems to survive when the thread leaves the method MakeCounter().
  • It seems that several occurrences of this ‘surviving' local variable exist!

Note that in .NET 2005, the CLR and the IL language haven't been tweaked to support the anonymous method feature. The mystery might stems from the compiler. It's a nice example of ‘syntactic sugar'. Let's analyse the assembly:

Introduce to C# anonymous methods

This analysis makes things clear because:

  • The compiler doesn't only create a new method as we saw in the previous section. It utterly creates a new class named <>c__DisplayClass1 in this example.
  • This class has an instance method called <MakeCounter>b__0(). This method has the body of our anonymous method.
  • This class has also an instance field called counter. This field keeps track of the state of the local variable counter. We say the local variable counter has been captured by the anonymous method.
  • The method instantiates the class <>c__DisplayClass1. Moreover it initializes the field counter of the created instance.
  • Notice that the method MakeCounter() doesn't have any local variable. For the variable counter, it uses the same named field of the generated instance of the class <>c__DisplayClass1.

Before explaining why the compiler has this surprising behaviour, let's go further to get a thorough understanding of its work.

Captured local variables and code visibility

The following subtle example has been mentioned in the blog of Brad Abrams.

using System;
using System.Threading;
class Program{
   static void Main(string[] args){
      for (int i = 0; i < 5; i++)
         ThreadPool.QueueUserWorkItem(delegate {  Console.WriteLine(i); }, null);
       Console.ReadLine();
   }
}
      

This program outputs in a non-deterministic way something like:

0
1
5
5
5
      

This result compels us to infer that the local variable i is shared amongst all threads. The execution is non-deterministic because the Main() method and our closure are executed simultaneously by several threads. To make things clear, here is the decompiled code of the Main() method:

private static void Main(string[] args){
   bool flag1;
   Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1();
   class1.i = 0;
   goto Label_0030;
Label_000F:
   ThreadPool.QueueUserWorkItem(new WaitCallback(class1.<Main>b__0), null);
   class1.i++;
Label_0030:
   flag1 = class1.i < 5;
   if (flag1){
      goto Label_000F;
   }
    Console.ReadLine();
}
      

Notice that the fact that the value 5 is printed indicates that the Main() method is out of the loop at the moment of printing.

The following version of this program has a deterministic execution:

using System;
using System.Threading;
class Program{
    static void Main(string[] args){
        for (int i = 0; i < 5; i++){
            int j = i;
            ThreadPool.QueueUserWorkItem(delegate {  Console.WriteLine(j); }, null);
        }
         Console.ReadLine();
    }
}
      

This time, the program outputs:

0
1
2
3
4
      

This behavior stems from the fact that the local variable j is captured for each iteration. Here is the decompiled code of the Main() method:

private static void Main(string[] args){
   Program.<>c__DisplayClass1 class1;
   bool flag1;
   int num1 = 0;
   goto Label_0029;
Label_0004:
   class1 = new Program.<>c__DisplayClass1();
   class1.j = num1;
   ThreadPool.QueueUserWorkItem(new WaitCallback(class1.<Main>b__0), null);
   num1++;
Label_0029:
   flag1 = num1 < 5;
   if (flag1){
      goto Label_0004;
   }
    Console.ReadLine();
}
      

This sheds light on the fact that capturing local variables with anonymous methods is not an easy thing. You should always take care when using this feature.

Note that a captured local variable is no longer a local variable. If you access such a variable with some unsafe code, you might have fixed it before (with the C# keyword fixed).

Captured arguments

Arguments of a method can always be deemed as local variables. Therefore, C# v2 allows an anonymous method to use arguments of its outer method. For instance:

using System;
class Program{
   delegate void DelegateTypeCounter();
   static DelegateTypeCounter MakeCounter(string counterName){
      int counter = 0;
      DelegateTypeCounter delegateInstanceCounter = delegate{
          Console.WriteLine(counterName + (++counter).ToString());
      };
      return delegateInstanceCounter;
   }
   static void Main(){
      DelegateTypeCounter counterA = MakeCounter("Counter A:");
      DelegateTypeCounter counterB = MakeCounter("Counter B:");
      counterA();
      counterA();
      counterB();
      counterB();
      Console.ReadLine();
   }
}
      

This program outputs:

Counter A:1
Counter A:2
Counter B:1
Counter B:2
      

Nevertheless, an anonymous method can't capture an out or ref argument. This restriction is readily understandable as soon as you realize that such an argument can't be seen as a local variable. Indeed, such an argument survives the execution of the method.

Captured fields

An anonymous method can access members of its outer class. The case of static member's access is readily understandable since there is one and only one occurrence of any static field in the domain application. Thus, there is nothing like ‘capturing' a static field.

An instance member's access is less obvious. To clarify this point, remember that the this reference that allows access to instance members, is a local variable of the outer instance method. Therefore, the this reference is captured by the anonymous method. Let's analyze the following example:

using System;
delegate void DelegateTypeCounter();
class CounterMaker{
   string m_Name; // Un champ d'instance
   internal CounterMaker(string name) { m_Name = name; }
   internal DelegateTypeCounter MakeCounter(string counterName){
      int counter = 0;
      DelegateTypeCounter delegateInstanceCounter = delegate{
          Console.Write(counterName +(++counter).ToString());
          Console.WriteLine(" Counter built by:" + m_Name); // on aurait pu écrire this.m_Name
      };
      return DelegateInstanceCounter;
   }
}
class Program{
   static void Main(){
      CounterMaker counterMaker1 = new CounterMaker("Factory1");
      CounterMaker counterMaker2 = new CounterMaker("Factory2");
      DelegateTypeCounter counterA = counterMaker1.MakeCounter("Counter A:");
      DelegateTypeCounter counterB = counterMaker1.MakeCounter("Counter B:");
      DelegateTypeCounter counterC = counterMaker2.MakeCounter("Counter C:");
      counterA();  counterA();
      counterB();  counterB();
      counterC();  counterC();
      Console.ReadLine();
   }
}
      

This program outputs:

Counter A:1 Counter built by:Factory1
Counter A:2 Counter built by:Factory1
Counter B:1 Counter built by:Factory1
Counter B:2 Counter built by:Factory1
Counter C:1 Counter built by:Factory2
Counter C:2 Counter built by:Factory2
      

Let's decompile the MakeCounter() method to expose the this reference capture:

internal DelegateTypeCounter MakeCounter(string counterName){
    CounterMaker.<>c__DisplayClass1 class1 = new CounterMaker.<>c__DisplayClass1();
    class1.<>4__this = this;
    class1.counterName = counterName;
    class1.counter = 0;
    return new DelegateTypeCounter(class1.<MakeCounter>b__0);
}
      

Notice that the this reference cannot be captured by an anonymous method that is defined in a structure. Here is the compiler error:

Advanced uses of anonymous methods

Definitions: closure and lexical environment

A closure is a function that captures values of its lexical environment, when it is created at run-time. The lexical environment of a function is the set of variables visible from the concerned function.

In previous definitions, we used carefully the terms when and from. It indicates that the notion of closure pinpoints something that exists at run-time (as the concept of object). It indicates also that the notion of lexical environment pinpoints something that exists in the code, i.e at compile-time (as the concept of class). Consequently, you can consider that the lexical environment of a C# v2 anonymous method is the class generated by the compiler. Following the same idea, you can consider that an instance of such a generated class is a closure.

The definition of closure also implies the notion of creating a function at run-time. Mainstream imperative languages such as C, C++, C#1, Java or VB.NET1 don't support the ability to create an instance of a function at run-time. This feature stems from functional languages such as Haskell or Lisp. Thus C# v2 goes beyond imperative languages by supporting closures. However, C# v2 is not the first imperative language that supports closures since Perl and Ruby also have this feature.

Ramblings on closures

A function computes its results both from values of its arguments and from the context that surrounds its invocation. You can consider this context as a set of background data. Thus, arguments of a function can be seen as foreground data. Therefore, the decision that an input data of a function must be an argument must be taken from the relevance of the argument for the computation.

Generally, when using object languages, the context of a function (i.e. the context of an instance method) is the state of the object on which it is invoked. When programming with non object oriented imperative languages such as C, the context of a function is the values of global variables. When dealing with closures, the context is the values of captured variables when the closure is created. Therefore, as classes, closures are a way to associate behavior and data. In object oriented world, methods and data are associated thanks to the this reference. In functional world a function is associated with the values of captured variables. To make thinks clear:

  • You can think of an object as a set of method attached to a set of data.
  • You can think of a closure as a set of data attached to a function.

Using closures instead of classes

The previous section implicitly implies that some sort of classes could be replaced by some anonymous methods. Actually, we already perform such replacement in our implementation of counter. The behavior is the increment of the counter while the state is its value. However, the counter implementation doesn't harness the possibility to pass argument to an anonymous method. The following example shows how to harness closures to perform parameterized computation on the state of an object:

using System;
class Program{
   delegate void delegateMultiplier(ref int integerToMultiply);
   static DelegateMultiplier BuildMultiplier(int multiplierParam){
      return delegate(ref int integerToMultiply){
         integerToMultiply *= multiplierParam;
      };
   }
   static void Main(){
      DelegateMultiplier multiplierPar8 = BuildMultiplier(8);
      DelegateMultiplier multiplierPar2 = BuildMultiplier(2);
      int aninteger = 3;
      multiplierPar8(ref aninteger);
      // here aninteger = 24
      multiplierPar2(ref aninteger);
      // here aninteger = 48
       Console.ReadLine();
   }
}
      

Here is another example that shows how to harness closures to perform parameterized computation to obtain a value from the state of an object:

using System;
class Article{
   public Article(decimal price) { m_Price = price; }
   private decimal m_Price;
   public decimal Price { get { return m_Price; } }
}
class Program{
   delegate decimal DelegateTaxComputer(Article article);
   static DelegateTaxComputer BuildTaxComputer(decimal tva){
      return delegate(Article article){
         return (article.Price * (100 + tva)) / 100;
      };
   }
   static void Main(){
      DelegateTaxComputer taxComputer19_6 = BuildTaxComputer(19.6m);
      DelegateTaxComputer taxComputer5_5 = BuildTaxComputer(5.5m);
      Article article = new Article(97);
      Console.WriteLine("Price (Tax=19.6%) : "+ taxComputer19_6(article));
      Console.WriteLine("Price (Tax=5.5%)  : "+ taxComputer5_5(article));
      Console.ReadLine();
   }
}
      

Using closures as functors

For those who have fiddled with the C++'s Standard Template Library (STL), functors as you might recall are an elegant programming style that allows you to tweak a collection in any way with a single line of code. The good news is that anonymous methods make this feature a reality in the .NET world.

The name functor stands for function-object. Basically, a functor is a parameterized behaviour. Functors are particularly suited to manipulate collections because you can apply its behaviour to all elements of a collection with a single line of code. For instance, suppose that you have a list of articles and that you want to select articles that cost more than a given price. You would write:

List<Article> SelectExpensiveArticle(decimal expensiveThreshold, List<Article> listIn){
   List<Article> listOut = new List<Article>();
   foreach (Article article in listIn)
      if( article.Price > expensiveThreashold )
         listOut.Add(article);
   return listOut;
}
      

With C# v2's functors you can code the same behaviour like this:

List<Article> SelectExpensiveArticle2(decimal expensiveThreshold,
		List<Article> listIn){
   return listIn.FindAll(delegate(Article article) { return (article.Price >
   		expensiveThreashold); });
}
      

Functors are good at four things:

  • Selecting elements of a list that satisfy a given predicate (in the previous example, the predicate is: Is it true that the price is more expensive that the given threshold?).
  • Performing an action on every element of a list.
  • Sorting elements of a list.
  • Building a new list from a list. Both lists have the same number of elements. Each element of the new list is computed from its corresponding element in the former list. Elements of the new list can have a different type than the type of elements of the former list.

Here is a small program that illustrates these functionalities:

using System.Collections.Generic;

class Program{
   class Article{
      public Article(decimal price,string name){Price = price;Name = name;}
      public readonly decimal Price;
      public readonly string  Name;
   }
   static void Main(){

      // Build a list of integers and a list of Article
      List<int> integers = new List<int>();
      for(int i=1; i<=10; i++) integers.Add(i);

      List<Article> articles = new List<Article>();
      articles.Add(new Article(23,"Chair"));
      articles.Add(new Article(56,"Table"));


      // selecting even integers from a list of integers
      // implicit use of the delegate      public delegate bool 
			System.Predicate<T>(T obj) 
      List<int> even = integers.FindAll(delegate(int i){return i%2==0; });

      // Perform an action with every elements: sum up elements of an integer list
      // implicit use of the delegate      public delegate void 
System.Action<T>(T obj)
      int sum = 0;
      integers.ForEach(delegate(int i) { sum += i; });

      // Perform an action on every elements: modify each elements of a list
      // implicit use of the delegate      public delegate void
	  		System.Action<T>(T obj)
      decimal increment = 10;
      articles.ForEach(delegate(Article x) { x.Price += increment; });

      // sort elements of a complex type
      // implicit use of the delegate      public delegate int
	  		System.Comparison<T>(T x,T y)
      articles.Sort(delegate(Article x, Article y){return
	  		Comparer<decimal>.Default.Compare(x.Price,y.Price); });

      // get a list of prices from a list of articles
      // implicit use of the delegate      delegate U System.Converter<T,U>(T from)
      List<decimal> articlesPrices = articles.ConvertAll<decimal>(
	  		delegate(Article article) { return (decimal)article.Price; });

      System.Console.ReadKey();
   }
}
      

new, Courier, mono">System.Collections.Generic.List<T> and the enhanced new, Courier, mono">System.Array classes are the only classes of the .NET framework that take benefits of functors. Here is the exhaustive list of methods that support functors:

public class List<T> : System.Collections.Generic.IList<T>,
		System.Collections.Generic.ICollection<T>,
		System.Collections.Generic.IEnumerable<T>, System.Collections.IList,
		System.Collections.ICollection, System.Collections.IEnumerable{
   public int FindIndex(Predicate<T> match);
   public int FindIndex(int index, Predicate<T> match);
   public int FindIndex(int index, int count, Predicate<T> match);

   public int FindLastIndex(Predicate<T> match);
   public int FindLastIndex(int index, Predicate<T> match);
   public int FindLastIndex(int index, int count, Predicate<T> match);

   public List<T> FindAll(Predicate<T> match);
   public T Find(Predicate<T> match);
   public T FindLast(Predicate match);

   public bool Exists(Predicate<T> match);
   public bool TrueForAll(Predicate<T> match);

   public int RemoveAll(Predicate<T> match);
   public void ForEach(Action<T> action);
   public void Sort(Comparison<T> Comparison);
   public List<U> ConvertAll<U>(Converter<T,U> converter);
   ...
}
public class Array{
   public static int FindIndex<T>(T[] array, int startIndex,
                                  int count, Predicate<T> match);
   public static int FindIndex<T>(T[] array, int startIndex,
                                  Predicate<T> match);
   public static int FindIndex<T>(T[] array, Predicate<T> match);

   public static int FindLastIndex<T>(T[] array, int startIndex,
                                      int count, Predicate<T> match);
   public static int FindLastIndex<T>(T[] array, int startIndex,
                                      Predicate<T> match);
   public static int FindLastIndex<T>(T[] array, Predicate<T> match);

   public static T[] FindAll<T>(T[] array, Predicate<T> match);
   public static T Find<T>(T[] array, Predicate<T> match);
   public static T FindLast<T>(T[] array, Predicate<T> match);

   public static bool Exists<T>(T[] array, Predicate<T> match);
   public static bool TrueForAll<T>(T[] array, Predicate<T> match);

   public static void ForEach<T>(T[] array, Action<T> action);
   public static void Sort<T>(T[] array, System.Comparison<T> comparison);
   public static U[] ConvertAll<T, U>(T[] array, Converter<T, U> converter);
   ...
}
      

Conclusion

After going through the basics of anonymous methods, we discovered that this feature is more complex and more useful than expected at first glance. This new C# v2 feature is an implementation of the notion of closures of functional languages. In C# v2, closures can be seen as some syntactic sugar and understanding the work of the compiler is essential. We saw that using closures incorrectly can be harmful for code clarity. We saw that closures are a good means to replace some small classes. They can also dramatically enhance list manipulation.

In a next article we'll cover iterators of C# v2. We'll underline the work of the compiler in order to use properly this feature.

References:

The C# programming language by Anders Hejlsberg, Scott Wiltamuth, Peter Golde

Create Elegant Code with Anonymous Methods, Iterators, and Partial Classes by Juval Lowy

Implementation of Closures (Anonymous Methods) in C# 2.0 (Part 6) Roshan James's blog

Closures in CLR 2.0 Antonio Cisternino's blogs

Fun with Anonymous Methods Brad Abrams's blog

What is closure c2 Wiki

Anonymous Methods c2 Wiki

Anonymous Methods, Part 2 of ? GrantRi's WebLog [MS]

Charming Python: Functional programming in Python, Part 2 by David Mertz

Authors

Introduce to C# anonymous methods
Patrick Smacchia is a .NET MVP involved in software development for over 15 years. After graduating in mathematics and computer science from the ENSEEIHT school, he has worked on software in a variety of fields including stock exchange at Société Générale, airline ticket reservation system at Amadeus as well as a satellite base station at Alcatel. He's currently a software consultant and trainer on .NET technologies as well as the author of the freeware NDepend which provides numerous metrics and caveats on any compiled .NET application.

继续阅读