C#2 匿名方法






4.78/5 (30投票s)
2006 年 6 月 15 日
13分钟阅读

116071

397
本文介绍C# 2.0语言中新增的一项名为“匿名方法”的新特性。与泛型不同,此特性不涉及新的IL指令。所有的魔法都在编译器层面完成。
|
以下文章摘自《Practical .NET2 and C#2》一书的第14章。
目录
本文介绍C# 2.0语言中新增的一项名为匿名方法的新特性。与泛型不同,此特性不涉及新的IL指令。所有的魔法都在编译器层面完成。
C# 2 匿名方法简介
让我们开始通过一个简单的C# 1.x 版本程序来学习C# 2.0的匿名方法。该程序首先通过委托引用并调用一个方法。
class Program {
delegate void DelegateType();
static DelegateType GetMethod(){
return new DelegateType(MethodBody);
}
static void MethodBody() {
System.Console.WriteLine("Hello");
}
static void Main() {
DelegateType delegateInstance = GetMethod();
delegateInstance();
delegateInstance();
}
}
现在,我们用C# 2.0的匿名方法重写同一个程序。
class Program {
delegate void DelegateType();
static DelegateType GetMethod() {
return delegate() { System.Console.WriteLine("Hello"); };
}
static void Main() {
DelegateType delegateInstance = GetMethod();
delegateInstance();
delegateInstance();
}
}
你应该注意到:
- 在C# 2.0中,`delegate` 关键字有了一个新的用法。当C# 2.0编译器在类的主体中遇到 `delegate` 关键字时,它期望后面跟着一个匿名方法体。
- 可以将匿名方法赋给一个委托引用。
- 我们明白了为什么这个特性被称为匿名方法:在 `GetMethod()` 方法体中定义的 `delegate` 实例的方法是没有命名的。然而,由于它被一个委托实例引用,所以是可以调用的。
你还应该注意到,可以使用 `+=` 操作符使一个委托实例引用多个方法(无论是匿名的还是非匿名的)。
using System;
class Program{
delegate void DelegateType();
static void Main(){
DelegateType delegateInstance = delegate() {
Console.WriteLine("Hello"); };
delegateInstance += delegate() { Console.WriteLine("Bonjour"); };
delegateInstance();
}
}
正如你所料,这个程序输出:
Hello
Bonjour
匿名方法可以接受参数
如下例所示,匿名方法可以接受任何类型的参数。你还可以使用 `ref` 和 `out` 关键字来调整参数的传递方式。
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 ) {
System.Console.WriteLine( "Hello valParam:{0} refTypeParam:{1}",
valTypeParam, refTypeParam);
refParam++;
outParam = 9;
return valTypeParam;
}; // End of the body of the anonymous method.
}
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 );
System.Console.WriteLine( "i:{0} j:{1} refVar:{2} outVar:{3}",
i, j, refVar, outVar);
}
}
此程序输出:
Hello valParam:1 refTypeParam:one
Hello valParam:2 refTypeParam:two
i:1 j:2 refVar:7 outVar:9
正如你所见,返回类型未在匿名方法声明中定义。匿名方法的返回类型由C# v2 编译器根据它被赋值的委托的返回类型推断得出。这个类型总是已知的,因为编译器强制将任何匿名方法赋值给一个委托。
匿名方法不能被标记为特性。此限制意味着你不能在匿名方法的参数列表中使用 `param` 关键字。事实上,使用 `param` 关键字会强制编译器将相关方法标记为 `ParamArray` 特性。
using System;
class Program {
delegate void DelegateType( params int[] arr );
static DelegateType GetMethod() {
// Compilation error: param is not valid in this context.
return delegate( params int[] arr ){ Console.WriteLine("Hello");};
}
}
语法上的一个细节
可以声明一个没有签名的匿名方法,也就是说,如果你的匿名方法不接受任何参数,你不需要在 `delegate` 关键字后面写一对括号。在这种情况下,你的方法可以被赋值给任何返回 `void` 类型且不带 `out` 参数的委托实例。显然,这种匿名方法无法访问通过委托调用传递的参数。
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 );
}
}
匿名方法和泛型
如下例所示,匿名方法的参数可以是泛型类型。
class Foo<T> {
delegate void DelegateType( T t );
internal void Fct( T t ) {
DelegateType delegateInstance = delegate( T arg ){
System.Console.WriteLine( "Hello arg:{0}" , arg.ToString() ); };
delegateInstance( t );
}
}
class Program {
static void Main() {
Foo<DOUBLE> inst = new Foo <DOUBLE>();
inst.Fct(5.5);
}
}
在 .NET 2.0 中,委托类型可以用泛型参数声明。匿名方法可以被赋值给这种类型的委托实例。你只需要在赋值的双方解决类型参数即可。
class Program{
delegate void DelegateType<T>( T t );
static void Main() {
DelegateType<double> delegateInstance = delegate( double arg ) {
System.Console.WriteLine( "Hello arg:{0}" , arg.ToString() );
};
delegateInstance(5.5);
}
}
匿名方法在实际中的应用
匿名方法特别适合定义需要通过委托调用的“小型”方法。例如,你可能会使用匿名方法来编写线程的入口点过程。
using System.Threading;
class Program{
static void Main(){
Thread thread = new Thread( delegate() {
System.Console.WriteLine( "ManagedThreadId:{0} Hello",
Thread.CurrentThread.ManagedThreadId );
} );
thread.Start();
System.Console.WriteLine( "ManagedThreadId:{0} Bonjour",
Thread.CurrentThread.ManagedThreadId );
}
}
此程序显示:
ManagedThreadId:1 Bonjour
ManagedThreadId:3 Hello
这类用途的另一个经典例子是Windows 窗体控件事件回调。
public class FooForm : System.Windows.Forms.Form {
System.Windows.Forms.Button m_Button;
public FooForm() {
InitializeComponent();
m_Button.Click += delegate( object sender, System.EventArgs args ) {
System.Windows.Forms.MessageBox.Show("m_Button Clicked");
};
}
void InitializeComponent() {/*...*/}
}
看起来匿名方法就像一项微小的语言增强。现在是时候深入了解一下,看看匿名方法远比这复杂,而且用途也更广泛。
C# 2 编译器与匿名方法
简单的方法
正如你所料,当匿名方法被编译时,编译器会在相关的类中创建一个新方法。
class Program {
delegate void DelegateType();
static void Main() {
DelegateType delegateInstance = delegate() {
System.Console.WriteLine("Hello"); };
delegateInstance();
}
}
以下是前一个程序编译后的程序集(使用Reflector 工具查看)。
确实,自动生成了一个名为 `
我们还注意到,生成了一个名为 `<>9_CachedAnonymousMethoddelegate1` 的 `delegateType` 类型的委托字段,用于引用我们的匿名方法。
值得注意的是,所有这些生成的成员都无法通过 C# IntelliSense 查看,因为它们的名称包含一对尖括号 `< >`。这种名称对于 IL/CLR 语法是有效的,但对于 C# 语法是不正确的。
捕获的局部变量
为了保持清晰和简单,我们还没有提及匿名方法可以访问其外部方法的局部变量。让我们通过以下示例来分析这个特性。
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();
System.Console.WriteLine( counter1() );
System.Console.WriteLine( counter1() );
System.Console.WriteLine( counter2() );
System.Console.WriteLine( counter2() );
}
}
此程序输出:
1
2
1
2
仔细想想,这可能会让你困惑。当线程离开 `MakeCounter()` 方法后,局部变量 `counter` 似乎仍然存在。而且,似乎存在这个“幸存”局部变量的两个实例!
请注意,在 .NET 2.0 中,CLR 和 IL 语言并没有为支持匿名方法特性进行修改。这种有趣的现象一定源于编译器。这是一个很好的“语法糖”示例。让我们来分析一下程序集。
分析清楚了,因为:
- 编译器不仅创建了一个新方法(正如我们在上一节看到的),它还完全创建了一个名为 ` c__DisplayClass1` 的新类(在此示例中)。
- 这个类有一个名为 `
b__0()` 的实例方法。这个方法包含了我们匿名方法的代码。 - 这个类还有一个名为 `counter` 的实例字段。这个字段跟踪局部变量 `counter` 的状态。我们说局部变量 `counter` 被匿名方法捕获了。
- 该方法实例化了 ` c__DisplayClass1` 类。此外,它还初始化了创建实例的 `counter` 字段。
请注意,`MakeCounter()` 方法没有局部变量。对于 `counter` 变量,它使用了生成的 ` c__DisplayClass1` 类实例的同一个字段。
在解释编译器为何会有这种令人惊讶的行为之前,让我们进一步深入以充分理解它的工作原理。
捕获的局部变量与代码复杂度
以下示例比预期要微妙。
using System.Threading;
class Program {
static void Main() {
for (int i = 0; i < 5; i++)
ThreadPool.QueueUserWorkItem( delegate {
System.Console.WriteLine(i); }, null);
}
}
此程序以非确定性的方式输出,类似:
0
1
5
5
5
这个结果迫使我们推断,局部变量 `i` 被所有线程共享。执行是非确定性的,因为 `Main()` 方法和我们的闭包(或匿名方法)由多个线程同时执行。为了说清楚,这是 `Main()` 方法的反编译代码。
private static void Main(){
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;
}
}
请注意,打印值5的事实表明,当显示完成时,`Main()` 方法已经完成了循环的执行。以下版本的程序具有确定性的执行。
using System.Threading;
class Program {
static void Main() {
for (int i = 0; i < 5; i++){
int j = i;
ThreadPool.QueueUserWorkItem(delegate {
System.Console.WriteLine(j); }, null);
}
}
}
这次,程序输出:
0
1
2
3
4
这种行为源于局部变量 `j` 在每次迭代时都被捕获。这是 `Main()` 方法的反编译代码。
private static void Main(){
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;
}
}
这揭示了用匿名方法捕获局部变量并非易事。使用此功能时应始终小心。
请注意,捕获的局部变量不再是局部变量。如果你使用不安全代码访问此类变量,你可能需要事先(使用 C# 的 `fixed` 关键字)固定它。
匿名方法访问外部方法的参数
方法的参数始终可以视为局部变量。因此,C# 2.0 允许匿名方法使用其外部方法的参数。例如:
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();
}
}
此程序输出:
Counter A:1
Counter A:2
Counter B:1
Counter B:2
然而,匿名方法无法捕获 `out` 或 `ref` 参数。一旦你意识到这种参数不能被视为局部变量,这种限制就很容易理解了。事实上,这种参数在方法执行后仍然存在。
匿名方法访问外部类的成员
匿名方法可以访问其外部类的成员。静态成员访问很容易理解,因为在应用程序域中只有一个静态字段的实例。因此,不存在“捕获”静态字段的情况。
对实例的成员访问不那么明显。为了阐明这一点,请记住,允许访问实例成员的 `this` 引用是外部实例方法的局部变量。因此,`this` 引用被匿名方法捕获。让我们分析以下示例。
delegate void DelegateTypeCounter();
class CounterBuilder {
string m_Name; // Un champ d’instance
internal CounterBuilder( string name ) { m_Name = name; }
internal DelegateTypeCounter BuildCounter( string counterName ) {
int counter = 0;
DelegateTypeCounter delegateInstanceCounter = delegate {
System.Console.Write( counterName +(++counter).ToString() );
// On aurait pu écrire this.m_Name.
System.Console.WriteLine(" Counter built by: " + m_Name);
};
return delegateInstanceCounter;
}
}
class Program {
static void Main() {
CounterBuilder cBuilder1 = new CounterBuilder( "Factory1" );
CounterBuilder cBuilder2 = new CounterBuilder( "Factory2" );
DelegateTypeCounter cA = cBuilder1.BuildCounter( "Counter A:" );
DelegateTypeCounter cB = cBuilder1.BuildCounter( "Counter B:" );
DelegateTypeCounter cC = cBuilder2.BuildCounter( "Counter C:" );
cA(); cA ();
cB(); cB();
cC(); cC();
}
}
此程序输出:
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
让我们反编译 `MakeCounter()` 方法以揭示 `this` 引用的捕获。
internal DelegateTypeCounter BuildCounter(string counterName){
CounterBuilder.<>c__DisplayClass1 class1 = new
CounterBuilder.<>c__DisplayClass1();
class1.<>4__this = this;
class1.counterName = counterName;
class1.counter = 0;
return new DelegateTypeCounter(class1.<BuildCounter>b__0);
}
请注意,`this` 引用不能被结构体中的匿名方法捕获。这是编译器错误:结构体中的匿名方法无法访问 'this
' 的实例成员。请考虑在匿名方法外部将 'this
' 复制到局部变量,并改用该局部变量。
匿名方法与闭包
定义:闭包与词法环境
闭包是一个函数,它在运行时创建时捕获其词法环境的值。函数的词法环境是从所关心的函数可见的变量集合。
在之前的定义中,我们仔细使用了“运行时”和“从”这两个词。这表明闭包的概念指向运行时存在的东西(如对象概念)。它还表明词法环境的概念指向代码中存在的东西,即编译时(如类概念)。因此,你可以认为 C# 2.0 匿名方法的词法环境是编译器生成的类。按照同样的思路,你可以认为这种生成类的实例是一个闭包。
闭包的定义还包含了“在运行时创建函数”的概念。主流命令式语言(如 C、C++、C# 1.0、Java 或 VB.NET 1.0)不支持在运行时创建函数实例的能力。这个特性源于函数式语言(如 Haskell 或 Lisp)。因此,C# 2.0 在支持闭包方面超越了命令式语言。然而,C# 2.0 并不是第一个支持闭包的命令式语言,因为 Perl 和 Ruby 也拥有这个特性。
关于闭包的杂谈
一个函数的结果计算既取决于其参数的值,也取决于围绕其调用的上下文。你可以将此上下文视为一组背景数据。因此,函数的参数可以看作是前台数据。因此,关于函数输入数据必须是参数的决定,应该从参数对计算的相关性来考虑。
通常,在使用面向对象语言时,函数的上下文(即实例方法的上下文)是调用它的对象的当前状态。在用非面向对象命令式语言(如 C)编程时,函数的上下文是全局变量的值。在处理闭包时,上下文是闭包创建时捕获变量的值。因此,像类一样,闭包是关联行为和数据的一种方式。在面向对象的世界中,方法和数据通过 `this` 引用关联。在函数式世界中,一个函数与捕获变量的值相关联。为了说清楚:
- 你可以将一个对象看作是一组附加到一组数据的方法。
- 你可以将一个闭包看作是附加到一个函数的数据集。
使用闭包代替类
上一节暗示某些类型的类可以用匿名方法替换。事实上,我们在计数器实现中已经执行了这种替换。行为是计数器的增量,而状态是它的值。然而,计数器实现并未利用传递参数给匿名方法的可能性。以下示例演示了如何利用闭包对对象的状态执行参数化计算。
class Program {
delegate void DelegateMultiplier( ref int integerToMultipl);
static DelegateMultiplier BuildMultiplier ( int multiplierParam ) {
return delegate( ref int integerToMultiply ) {
integerToMultiply *= multiplierParam;
};
}
static void Main() {
DelegateMultiplier multiplierBy8 = BuildMultiplier(8);
DelegateMultiplier multiplierBy2 = BuildMultiplier(2);
int anInteger = 3;
multiplierBy8( ref anInteger );
// Here, anInteger is equal to 24.
multiplierBy2( ref anInteger );
// Here, anInteger is equal to 48.
}
}
这是另一个示例,演示了如何利用闭包执行参数化计算,以从对象状态中获取值。
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 tax ) {
return delegate( Article article ) {
return ( article.Price * (100 + tax) ) / 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) );
}
}
理解在以上两个示例中使用闭包的强大之处在于,它们避免了我们创建小型类(实际上是由编译器隐式创建的)。
委托与闭包
仔细观察,我们注意到 .NET 1.x 中在实例方法上使用的委托在概念上接近闭包的概念。事实上,这样的委托同时引用数据(对象的当前状态)和行为。存在一个约束:行为必须是定义 `this` 引用类型的类的实例方法。
在 .NET 2.0 中,这种约束得到了最小化。由于 `Delegate.CreateDelegate()` 方法的某些重载,你现在可以引用静态方法的第一参数作为委托。例如:
class Program {
delegate void DelegateType( int writeNTime );
// This method is public to avoid problems of reflection
// on a non-public member.
public static void WriteLineNTimes( string s, int nTime ) {
for( int i=0; i < nTime; i++ )
System.Console.WriteLine( s );
}
static void Main() {
DelegateType deleg = System.Delegate.CreateDelegate(
typeof( DelegateType ),
"Hello",
typeof(Program).GetMethod( "WriteLineNTimes" )) as DelegateType;
deleg(4);
}
}
此程序显示:
Hello
Hello
Hello
Hello
请注意,在内部,框架和 CLR 的 2.0 版本完全重写了委托的实现。好消息是,通过委托调用方法现在效率更高。
匿名方法与函数对象
函数对象简介
System
命名空间包含四个新的委托类型,它们在操作和从集合中获取信息时特别有用。
namespace System {
public delegate void Action<T> ( T obj );
public delegate bool Predicate<T> ( T obj );
public delegate U Converter<T,U> ( T from );
public delegate int Comparison<T> ( T x, T y );
}
以下示例展示了使用这些委托实例在列表中可以执行的四种处理(请求、计算、排序和转换)。
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 bool IsEven(int i) { return i % 2 == 0; }
static int sum = 0;
static void AddToSum(int i) { sum += i; }
static int CompareArticle(Article x, Article y){
return Comparer<DECIMAL>.Default.Compare(x.Price, y.Price);
}
static decimal ConvertArticle(Article article){
return (decimal)article.Price;
}
static void Main(){
// Seek out every odd integers.
// Implicitly uses a ‘Predicate<T>’ delegate object.
List integers = new List();
for(int i=1; i<=10; i++) {
integers.Add(i);
}
List even = integers.FindAll( IsEven );
// Sum up items of the list.
// Implicitly uses an ‘Action<T>’ delegate object.
integers.ForEach( AddToSum );
// Sort items of type ‘Article’.
// Implicitly uses a ‘Comparison<T>’ delegate object.
List articles = new List();
articles.Add( new Article(5,"Shoes") );
articles.Add( new Article(3,"Shirt") );
articles.Sort( CompareArticle );
// Cast items of type ‘Article’ into ‘decimal’.
// Implicitly uses a ‘Converter<T,U>’ delegate object.
List<DECIMAL> artPrice = articles.ConvertAll<DECIMAL>( ConvertArticle );
}
}
使用过 C++ 的标准模板库(STL)的读者将识别出函数对象的概念。函数对象是一个参数化的过程。函数对象在对集合的所有元素执行相同操作时特别有用。在 C++ 中,我们重载了圆括号运算符来实现函数对象。在 .NET 中,函数对象采用委托实例的形式。事实上,在前一个程序中,隐式创建的四个委托实例就是函数对象的四个例子。
使用匿名方法和函数对象查询集合
如下例所示,C# 的匿名方法在实现函数对象方面非常适用。请注意,正如第二个函数对象存储元素之和到一个整数中一样,函数对象可以封装状态。
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(){
// Seek out every odd integers.
// Implicitly uses a ‘Predicate<T>’ delegate object.
List integers = new List();
for(int i=1; i<=10; i++) {
integers.Add(i);
}
List even =integers.FindAll( delegate(int i){ return i%2==0; });
// Sum up items of the list.
// Implicitly uses an ‘Action<T>’ delegate object.
int sum = 0;
integers.ForEach(delegate(int i) { sum += i; });
// Sort items of type ‘Article’.
// Implicitly uses a ‘Comparison<T>’ delegate object.
List articles = new List();
articles.Add( new Article(5,"Shoes") );
articles.Add( new Article(3,"Shirt") );
articles.Sort(delegate(Article x, Article y) {
return Comparer<DECIMAL>.Default.Compare(x.Price,y.Price); } );
// Cast items of type ‘Article’ into ‘decimal’.
// Implicitly uses a ‘Converter<T,U>’ delegate object.
List<DECIMAL> artPrice = articles.ConvertAll<DECIMAL> (
delegate(Article article) { return (decimal)article.Price; } );
}
}
列表Array 类对函数对象的支持
函数对象的使用仅限于 `List
public class List<T> : ... {
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);
...
}