匿名方法
在使用委托的时候,除了将一个已存在的方法赋给一个委托之外,还可以使用匿名方法的方式。使用匿名方法的时候,需要一个delegate关键字,并且方法的参数列表要和委托定义的完全一样。
代码语言:javascript复制//这里有一个匿名方法
Func<int, int, int> Add = delegate (int a, int b)
{
return a b;
};
Console.WriteLine($"操作的结果是:{Add(1, 2)}");
匿名方法会由编译器给定一个名字,无法由用户使用,用户也不需要去了解。很明显,在编写只调用一次的方法时候,匿名方法很有效果。如果一个方法要在多个地方调用,还是写成命名方法利用名字来调用更加合理。
匿名方法有两个注意事项。
- 在匿名方法中不能使用跳转语句跳转到匿名方法外部,同样的,匿名方法外部也不能跳转到方法内部。
- 在匿名方法内部不能访问不安全的代码,也不能访问方法外部的ref和out关键字。
lambda表达式
从C#3.0开始,可以利用lambda表达式来代替匿名方法。所以,任何可以使用委托的地方,都可以使用lambda表达式。例如,上面的 例子就可以利用lambda表达式重写一下。可以看出,lambda表达式确实比匿名方法更加简洁。
代码语言:javascript复制//这里有一个lambda表达式
Func<int, int, int> Add = (a, b) => a b;
Console.WriteLine($"操作的结果是:{Add(1, 2)}");
在这里,委托是一个接受两个int型参数并返回一个int值的类型,因此对应的lambda表达式也是接受两个参数并返回一个值。两个参数需要写到括号中,返回值直接由=>右面的表达式给出。因为泛型委托已经给出了具体的参数和返回值的类型,因此lambda表达式不需要写出返回值,编译器可以自动推断出来。如果像这样在=>右面只有一个表达式的话,编译器会计算出它的值并将它当做返回值。
如果方法只有一个参数的话,就不需要使用括号括起来。就像这样。
代码语言:javascript复制//只有一个参数的lambda表达式
Func<double, double> Sqrt = a => Math.Sqrt(a);
Console.WriteLine($"表达式的结果是:{Sqrt(16)}");
如果没有参数的话,就使用一对空括号。
代码语言:javascript复制//没有参数的lambda表达式
Func<double> GetPi = () => Math.PI;
Console.WriteLine($"表达式的结果是:{GetPi()}");
如果方法体需要有多条语句,就必须添加花括号和return语句。
代码语言:javascript复制//多行代码的lambda表达式
Func<int, int, int> Sub = (a, b) =>
{
int result = a - b;
return result;
};
Console.WriteLine($"表达式的结果是:{Sub(6, 5)}");
闭包
前面的lambda表达式使用的值都是由参数传递进去的,这样的lambda表达式的运算结果显而易见。但是如果lambda表达式中使用了一个外部的变量,而不是由参数传递进去的值,会发生什么情况呢。下面的代码,运行结果会是什么样的呢?
代码语言:javascript复制int someValue = 10;
Func<int, int> f = a => a someValue;
someValue = 20;
Console.WriteLine($"lambda表达式的结果是:{f(10)}");
实际上,在遇到Lambda表达式的时候,编译器会将其编译成一个匿名类,外部变量由匿名类的构造函数传进来。就像这样:
代码语言:javascript复制public class 匿名类
{
private int someValue;
public 构造方法(int someValue)
{
this.someValue=someValue;
}
public int 匿名方法(int a)
{
return a someValue;
}
}
当调用 lambda表达式的时候,会创建这个匿名类的一个新实例,并将外部变量由传递进去。所以,上面的代码在lambda表达式调用的时候,会使用someValue最新的值,因此上面的代码结果为30。
foreach语句中的闭包
在C#5.0中,foreach语句中的闭包的行为有了很大的变化。以下是《C#高级编程》中的一个例子,非常具有代表性。先猜猜语句运行的结果是什么。
代码语言:javascript复制List<int> values = new List<int> { 10, 20, 30 };
List<Func<int>> funcs = new List<Func<int>>();
foreach (int val in values)
{
funcs.Add(() => val);
}
foreach (Func<int> func in funcs)
{
Console.WriteLine((func()));
}
在C#5.0之前,这段代码的运行结果是三个30。原因如下:编译器在遇到foreach语句时,会将其转换为while循环。而在C#4中,编译器会将while循环变量创建在while语句外部,因此第一个循环结束之后val的值是30。由于lambda表达式在定义的时候不会获得val的值,只有在第二个循环调用的时候才会获得。因此结果就是三个30。
在C#5中,会在while循环中创建一个不同的局部变量,因此值会保留下来。所以代码的行为会更加自然。这段代码的结果是10,20,30。