深入理解C#(十二)

*第五章(C# 2:进入快速通道的委托(下))

匿名方法

使用匿名方法的内联委托操作

C# 1中的问题:用特定签名来实现委托是很常见的情况。有时只需要一个委托来做一件很简单的事,但是必须创建一个完整的新方法。

匿名方法允许指定一个内联委托实例的操作,作为创建委托实例表达式的一部分。

简单的匿名方法实例:处理一个参数

.NET 2.0有一个泛型委托类型Action,它的签名如下:

public delegate void Action<T>(T obj)

Action就是对T的一个实例执行某些操作。如Action可以反转字符串并打印出来,Action可以打印传给它的那个数的平方根,Action<IList>可以计算出传给它的所有数的平均值并打印。

实例:

1
2
3
4
5
6
Action<string> printReverse = delegate (string text)
{
char[] chars = text.ToCharArray();
Array.Reverse(chars);
Console.WriteLine(new string(chars));
};

匿名方法的语法:

delegate关键字,参数,代码块(定义对委托实例的操作)匿名方法的结果是一个委托实例,和调用普通方法一样调用委托。

1
printReverse("hello world");

逆变性不适用于匿名方法:必须指定和委托类型完全匹配的参数类型

匿名方法的是实现,是在IL中为源代码中断每个匿名方法创建一个方法

一个更精简的极端例子:体现匿名方法的一般用法,即作为传给另一个方法的参数使用。

1
2
3
4
5
6
7
8
List<int> x = new List<int>();
x.Add(5);
x.Add(10);
x.Add(15);
x.Add(20);
x.Add(25);

x.ForEach(delegate (int n) { Console.WriteLine(Math.Sqrt(n)); });

匿名方法的返回值

1
2
3
4
5
Predicate<int> isEven = delegate (int x)
{ return x % 2 == 0; };

Console.WriteLine(isEven(1));
Console.WriteLine(isEven(4));

没有必要再声明一个返回类型,因为编译器会检查是否所有可能返回值都兼容于委托类型声明的返回类型。

忽略委托参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 class IgnoredParametersAnonymousMethods
{
static void Main()
{
Button button = new Button();
button.Text = "Click me";
button.Click += delegate { Console.WriteLine("LogPlain"); };
button.KeyPress += delegate { Console.WriteLine("LogKey"); };
button.MouseClick += delegate { Console.WriteLine("LogMouse"); };

Form form = new Form();
form.AutoSize = true;
form.Controls.Add(button);
Application.Run(form);
}
}

一般写法:button.Click+=delegate(object sender,EventArgs e){...}

匿名方法中的捕获变量

匿名方法外部对变量的更改在匿名方法内部是可见的,反之亦然。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void Main()
{
string captured = "before x is created";

MethodInvoker x = delegate
{
Console.WriteLine(captured);
captured = "changed by x";
};

captured = "directly before x is invoked";
x();

Console.WriteLine(captured);

captured = "before second invocation";
x();
Console.Read();
}

创建委托实例不会导致执行

在整个方法中,我们使用的始终是同一个captured变量

捕获变量的实际用处

能简化避免专门创建一些类来存储一个委托需要处理的信息(除了作为参数传递信息之外)

1
2
3
4
List<Person> FindAllYoungerThan(List<Person> people,int limit)
{
return people.FinaAll(delegate(Person person){ return person.Age<limit;});
}

我们在委托实例内部捕获了limit参数,若不支持捕获变量,就不能使用作为参数传递的limit

捕获变量的延长生存期

对于一个捕获变量,只要还有任何委托实例在引用它,它就会一直存在

局部变量实例化

每声明一次局部变量,它就被实例化一次。

捕获变量的使用规则和小结

  • 只在复杂情况下使用
  • 捕获有for或foreach语句声明的变量之前,思考委托是否需要在循环迭代结束后延续,不是则在循环内另建一个变量来复制想要的值。
  • 若创建多个委托实例,且捕获了变量,思考是否希望它们捕捉同一个变量
  • 如果捕获的变量不发生改变,无需担心