C# 委托和事件(Lambda表达式)
回调(callback)函数是Windows编程的一个重要部分。C或C++编程背景,在许多Windows API中使用过回调。VB添加AddressOf关键字后,开发人员就可以利用以前一度受到限制的API。回调函数实际上是方法调用的指针也称为函数指针。.NET以委托的形式实现函数指针的概念。C中函数指针只不过是一个指向存储单元的指针,无法说出这个指针实际指向什么,像参数和返回类型等更不知晓。
Lambda表达式
从C# 3.0开始,就可以使用一种新语法把实现代码赋予委托:Lambda表达式。只要有委托参数类型,就可以使用Lambda表达式。
Lambda表达式的语法比匿名方法简单。如果所调用的方法有参数,且不需要参数,匿名方法的语法就比较简单。
using System;
using System.Collections.Generic;
SimpleDemos();
ClosureWithModification();
ClosureWithForEach();
void SimpleDemos()
{
Console.WriteLine(nameof(SimpleDemos));
Func<string, string> oneParam = s => $"change uppercase {s.ToUpper()}";
Console.WriteLine(oneParam("test"));
Func<double, double, double> twoParams = (x, y) => x * y;
Console.WriteLine(twoParams(3, 2));
Func<double, double, double> twoParamsWithTypes = (double x, double y) =>
{
return x * y;
};
Console.WriteLine(twoParamsWithTypes(4, 2));
Func<double, double> operations = x => x * 2;
operations += x => x * x;
ProcessAndDisplayNumber(operations, 2.0);
ProcessAndDisplayNumber(operations, 7.94);
ProcessAndDisplayNumber(operations, 1.414);
Console.WriteLine();
}
void ProcessAndDisplayNumber(Func<double, double> action, double value)
{
double result = action(value);
Console.WriteLine($"Value is {value}, result of operation is {result}");
}
void ClosureWithModification()
{
Console.WriteLine(nameof(ClosureWithModification));
int someVal = 5;
Func<int, int> f = x => x + someVal;
someVal = 7;
Console.WriteLine(f(3));
Console.WriteLine();
}
void ClosureWithForEach()
{
Console.WriteLine(nameof(ClosureWithForEach));
var values = new List<int>() { 10, 20, 30 };
var funcs = new List<Func<int>>();
foreach (var val in values)
{
funcs.Add(() => val);
}
foreach (var f in funcs)
{
Console.WriteLine(f());
}
Console.WriteLine();
}
参数
Lambda表达式有几种参数的方式。如果只有一个参数,只写出参数名就够。因为委托定义一个string参数,所有s的类型就是string。实现代码调用String.Format()方法来返回一个字符串,在调用该委托时,就把字符串写到控制台上:
Func<string,string>oneParam=s=>String.Format{
"change uppercase{0}",s.ToUpper());
Console.WriteLine(oneParam("test"));
}
如果委托使用多个参数,就把参数名放在花括号中,这里参数x和参数Y的类型是double,由Func<double,double,double>委托定义:
Func<double,double,double>twoParams=(x,y)=>x*y;
Console.WriteLine(twoParasWithTypes(4,2));
方便起见,可以在花括号中给变量名添加参数类型:
Func<double,double,double>twoParamsWithTypes=
(double x,double y)=>x*y;
Console.WriteLine(twoParasWithTypes(4,2));
多行代码
如果Lambda表达式只有一条语句,在方法块内就不需要花括号和return语句,因为编译器会添加一条隐式的return语句。
Func<double,double>square=x=>x*x;
添加花括号、return语句和分好是完全合法的,通常这比不添加这些符号更容易阅读:
Func<double,double>square=x=>
{
return x*x;
}
但是,如果在Lambda表达式的实现代码中需要多条语句,就必须添加花括号和return语句:
Func<string,string>lamdad=param=>
{
param+=mid;
param+="and this was added to the string.";
return param;
};
Lambda表达式外部的变量
通过Lambda表达式可以访问Lambda表达式块外部的变量。下面的示例中,Fun<int,int>类型的Lambda表达式需要一个int参数,返回一个int。该Lambda表达式的参数用变量x定义。实现代码还访问Lambda表达式外部的变量someVal。只要不认为在调用f时,Lambda表达式创建一个以后使用的新方法。
int someVal=5;
Func<int,int>f=x=>x+someVal;
匿名类包含一个匿名方法,其实现代码、参数和返回类型由Lambda表达式定义:
public class AnonymousClass
{
private int someVal;
public AnonymousClass(int someVal)
{
this.someVal=someVal;
}
public int AnonymousClass(int x)
{
return x+someVal;
}
}
使用Lambda表达式并调用该方法,会创建匿名类的一个实例,并传递调用该方法时变量的值。
Lambda表达式可以用于类型时一个委托的任意地方,类型是Expression或Expression时,也可以使用Lambda表达式。