C# 委托(delegate)、泛型委托和Lambda表达式
1、从数据结构来讲,委托是和类一样是一种用户自定义类型。
2、委托是方法的抽象,它存储的就是一系列具有相同参数和返回类型的方法的地址。调用委托的时候,委托包含的所有方法将被执行。
# 委托声明、实例化和调用
1、声明
委托是一种特殊的类,因此委托的声明与类的声明方法类似,在任何可以声明类的地方都可以声明委托。委托声明用delegate关键字,同时委托要指明方法参数和返回值,写法与方法类似。综合类的声明和方法的声明,委托声明写成如下形式:
[访问修饰符] delegate 返回值类型 委托名 (形参列表);
public delegate void MyDel();//定义了一个委托MyDel,它可以注册返回void类型且没有参数的函数
public delegate void MyDel1(string str);//定义了一个委托MyDel1,它可以注册返回void类型且有一个string作为参数的函数
public delegate int MyDel2(int a,int b);//定义了一个委托MyDel2,它可以注册返回int类型且有两个int作为参数的函数
与普通类的使用方法相同,声明了委托之后,我们必须给委托传递一个具体的方法,才能在运行时调用委托实例。委托实例包含了被传递给它的方法的信息,在运行时,调用委托实例就相当于执行它当中的方法。
委托实例化格式如下:
委托类名 委托实例名 = new 委托类名(Target) ;
其中,委托实例名是自定义的名称,Target是要传入的方法的名称。注意,Target是方法的引用,不能带()。带()的话是该方法的调用。区分引用和调用。
委托的实例化还有一种简单的方法:
委托类名 委托实例名 = Target;
在需要委托实例的地方直接传入Target引用即可,C#编译器会自动根据委托类型进行验证,这称为“委托推断”。
MyDel2 testDel=new MyDel2(Add);
MyDel2 testDel1 = Add;
3、委托实例的调用
委托实例等价于它当中实际方法,因此可以使用反射的Invoke()方法调用委托实例,也可以直接在委托实例后加上()进行调用。
int num = testDel(1,2);
int num1 = testDel.Invoke(1, 2);
4、委托完整的简单示例
namespace delegateTest
{
public delegate int MyCalculator(int num1, int num2);
class Program
{
static void Main(string[] args)
{
MyCalculator myCal=new MyCalculator(Add);
int addNum= myCal(1,2);
MyCalculator myCal1 = Sub;
int subNum = myCal1.Invoke(1, 2);
Console.WriteLine("addNum:{0},subNum:{1}", addNum, subNum);
int calNum = Calculate(1, 2, Add);
Console.WriteLine("calNum:{0}", calNum);
}
static int Add(int num1, int num2)
{
Console.WriteLine("num1 + num2={0}",num1 + num2);
return num1 + num2;
}
static int Sub(int num1, int num2)
{
Console.WriteLine("num1 - num2={0}", num1 - num2);
return num1 - num2;
}
static int Calculate(int num1,int num2,MyCalculator calDel)
{
return calDel(num1,num2);
}
}
}
控制台打印结果:
num1 + num2=3
num1 - num2=-1
addNum:3,subNum:-1
num1 + num2=3
calNum:3
#泛型委托
我们每次要使用一个委托时,都需要先声明这个委托类,规定参数和返回值类型,然后才能实例化、调用。为了简化这个过程, .NET 框架为我们封装了三个泛型委托类,因此大部分情况下我们不必再声明委托,可以拿来直接实例化使用,方便了我们的日常写代码。
这三种泛型委托包括:Func委托、Action委托和Predicate委托。
1、Func委托
Func委托代表着拥有返回值的泛型委托。Func有一系列的重载,形式如 Func<T1,T2, ... TResult>,其中TResult代表委托的返回值类型,其余均是参数类型。只有一个T时,即Func,代表该委托是无参数的。.NET封装了最多16个输入参数的Funct<>委托。
需要特别注意的是,若方法没有返回值,即返回 void ,由于 void 不是数据类型,因此不能定义Func委托。返回 void 的泛型委托见下文的Action。
Func的使用方法与一般的委托相同。例如上面的案例可改写如下:
namespace delegateTest
{
class Program
{
static void Main(string[] args)
{
int calNum = Calculate(1, 2, Sub);
Console.WriteLine("calNum:{0}", calNum);// -1
}
static int Calculate(int num1, int num2, Func<int, int, int> calDel)
{
return calDel(num1,num2);
}
static int Sub(int num1, int num2)
{
Console.WriteLine("num1 - num2={0}", num1 - num2);
return num1 - num2;
}
}
}
2、Action委托
Action委托代表返回值为空 void 的委托,它也有一些列重载,最多拥有16个输入参数。用法与Func相同。
namespace delegateTest
{
class Program
{
static void Main(string[] args)
{
DoSome("hello",Say);// hello
}
static void DoSome(string str,Action<string> doAction)
{
doAction(str);
}
static void Say(string str)
{
Console.WriteLine(str);
}
}
}
3、Predicate委托
这个一般用的较少,它封装返回值为bool类型的委托,可被Func代替。
#匿名委托
采用匿名方法实例化的委托称为匿名委托。
每次实例化一个委托时,都需要事先定义一个委托所要调用的方法。为了简化这个流程,C# 2.0开始提供匿名方法来实例化委托。这样,我们在实例化委托时就可以 “随用随写” 它的实例方法。
使用的格式是:
委托类名 委托实例名 = delegate (args) { 方法体代码 } ;
这样就可以直接把方法写在实例化代码中,不必在另一个地方定义方法。当然,匿名委托不适合需要采用多个方法的委托的定义。
使用匿名方法,以上代码可改写为:
MyCalculator myCal2 = delegate(int num1, int num2)
{
//打印匿名方法的名字
Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name); // <Main>b__0
return num1 + num2;
};
int num11= myCal2(1,2);//3
需要说明的是,匿名方法并不是真的“没有名字”的,而是编译器为我们自动取一个名字。
#Lambda表达式
纵然匿名方法使用很方便,可惜她很快就成了过气网红,没能领多长时间的风骚。如今已经很少见到了,因为delegate关键字限制了她用途的扩展。自从C# 3.0开始,她就被Lambda表达式取代,而且Lambda表达式用起来更简单。Lambda表达式本质上是改进的匿名方法。
1、表达式Lambda
当匿名函数只有一行代码时,可采用这种形式。例如:
MyCalculator myCal = (num1, num2) => num1 + num2;
int num = myCal(1, 2);// 3
其中=>符号代表Lambda表达式,它的左侧是参数,右侧是要返回或执行的语句。参数要放在圆括号中,若只有一个参数,为了方便起见可省略圆括号。有多个参数或者没有参数时,不可省略圆括号。
相比匿名函数,在表达式Lambda中,方法体的花括号{}和return关键字被省略掉了。
2、语句Lambda
当匿名函数有多行代码时,只能采用语句Lambda。
MyCalculator myCal = (int num1, int num2)=>
{
Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name);
return num1 + num2;
};
int num = myCal(1, 2);// 3
语句Lambda不可以省略{}和return语句。
3、Lambda的主要用处
实际中用到Lambda表达式的地方大都是委托,例如linq的对集合类的扩展查询方法;
很多架构的搭建需要调用自定义方法,也离不开委托;
事件机制是基于委托的;
等等。
#多播委托
实例化委托时必须将一个匹配函数注册到委托上来实例化一个委托对象,但是一个实例化委托不仅可以注册一个函数还可以注册多个函数,注册多个函数后,在执行委托的时候会根据注册函数的注册先后顺序依次执行每一个注册函数。
函数注册委托的原型:
<委托类型> <实例化名> +=new <委托类型> (<注册函数>);
注意:委托必须先实例化以后,才能使用+=注册其他方法。如果对注册了函数的委托实例从新使用=号赋值,相当于是重新实例化了委托,之前在上面注册的函数和委托实例之间也不再产生任何关系。 有+=注册函数到委托,也有-=解除注册;
<委托类型> <实例化名> -=new <委托类型> (<注册函数>);
注意:如果在委托注册了多个函数后,如果委托有返回值,那么调用委托时,返回的将是最后一个注册函数的返回值。
MyCalculator multiCal=new MyCalculator(Add);
multiCal += Sub;
int num1 = multiCal(1, 2); // -1
multiCal -= Sub;
int num2 = multiCal(1, 2); // 3