C#基础篇——委托
前言
在本章中,主要是借机这个C#基础篇的系列整理过去的学习笔记、归纳总结并更加理解透彻。
在.Net开发中,我们经常会遇到并使用过委托,如果能灵活的掌握并加以使用会使你在编程中游刃有余,然后对于很多接触C#时间不长的开发者而言,较好的理解委托和事件并不容易。
本节主要是讲述对委托的定义、委托的使用、多播委托、泛型委托、匿名方法、Func和Action委托、Lambda委托,并对它们进行讨论。
说明
简单说它就是一个能把方法当参数传递的对象,而且还知道怎么调用这个方法,同时也是粒度最小的“接口”(约束了指向方法的签名)。
开始
1.定义委托
委托:是一种定义方法签名的类型。 当实例化委托时,可以将其实例与任何具有兼容签名的方法相关联。 可以通过委托实例调用方法。
这里引用一个网友的说法:
某人有三子,让他们各自带一样东西出门,并带回一头猎物。
上面一句话可以理解为父亲对儿子的委托:猎物 办法(工具 某工具)-->delegate 猎物(返回值) 带回猎物(委托名)(工具(参数类型) x)-->delegate int GetValue(int i)
三个人执行委托的方法各不相同
兔子 打猎(工具 弓)-public static int GetValue1(int i){ return i; }
野鸡 买(工具 钱)-public static int GetValue2(int i){ return i*2; }
狼 诱捕(工具 陷阱)-public static int GetValue3(int i){ return i*i; }
2.简单的使用
一个委托类型定义了该类型的实例化时能调用的一类方法,这些方法含有同样的返回类型和同样参数(类型和参数个数相同)
比如:定义一个委托
delegate int Calculator (int x);
此委托适用于有着int返回类型和一个int类型参数方法。
static int Double (int x) { return x * 2; }
创建一个委托实例,并将方法赋值给委托实例
Calculator c = new Calculator(Double);
//或者另一种写法
Calculator c = Double;
通过委托实例的调用
int result = c(2);
3.多播委托
在开发中,我们有时候会遇到要通过调用一个委托,同时可以执行多个方法的时候,就可以考虑用多播委托。调用多个委托需要多次显示调用这个委托。所有的委托实例都可以包含多个方法,实现多播功能。
这个打个比方:多播,就像一群程序员在瞬聘网填好了求职意向后,某天有个公司发布了一个和这些程序员求职意向刚好相匹配的工作,然后这些求职者都被通知了 - “有一份好工作招人啦,你们可以直接申请去上班了!”。
也就是说,一个委托实例不仅可以指向一个方法,还可以指向多个方法。
多播委托,提供了一种类似于流水线的钩子机制,只要加载到这条流水线上的委托,都会被顺序执行。因为所有的都继承自MulticastDelegate,因此所有的委托都具有多播特性
//声明一个委托,委托返回值为void
public delegate void Greetings(String name);
public static void Hello(String name)
{
Console.WriteLine("您好, {0}!", name);
}
public static void GoodBye(String name)
{
Console.WriteLine("再见, {0}!", name);
}
public static void Main()
{
Greetings greetings = Hello;
//使用+=给委托添加方法
greetings += GoodBye;
String name = "艾三元";
Console.WriteLine("这是一种调用方法:");
//第一种执行方式
greetings(name);
//第二种执行方式
Console.WriteLine("这是另一种使用方法");
//返回委托的调用列表。
Delegate[] delegates = greetings.GetInvocationList();
//注意这里的delegates列表中存储的是Greetings类型的委托
foreach (Greetings greeting in delegates)
{
greeting(name);
}
Console.ReadKey();
}
说明:
-
如果是多播委托,委托的签名就必须返回 void ,否则,返回值应送到何处?当委托只包含一个方法的时候,则可以通过所封装的方法发现其返回类型的声明,不一定必须是void。实际上,如果编译器发现某个委托返回 void ,就会自动假定这是一个多播委托。
-
“+=” 用来添加,“-=”用来从委托中删除方法调用
4.泛型委托
在之前的篇章中,我们已经学会了什么是泛型,因此,也方便我们理解泛型委托,简单的说,就是一种含有泛型参数的委托。
public delegate T Calculator<T>(T arg);
static int Double(int x) { return x * 2; }
static class Utility
{
public static void Calculate<T>(T[] values, Calculator<T> c)
{
for (int i = 0; i < values.Length; i++)
values[i] = c(values[i]);
}
}
static void Main(string[] args)
{
int[] values = { 11, 22, 33, 44 };
Utility.Calculate(values, Double);
foreach (int i in values)
Console.Write(i + " "); // 22 44 66 88
Console.ReadKey();
}
5. 匿名方法
匿名方法,是在初始化委托时候内联声明的方法。
每次实例化一个委托时,都需要事先定义一个委托所要调用的方法。为了简化这个流程,C# 2.0开始提供匿名方法来实例化委托。这样,我们在实例化委托时就可以 “随用随写” 它的实例方法。
static string GetNumber(string str)
{
return str;
}
delegate string DelNumber(string str);
static void Main(string[] args)
{
//声明一个名称为GetNumber的具名方法
DelNumber delNumber1 = GetNumber;
Console.WriteLine(delNumber1("这是具名方法"));
//匿名方法 ,未在别的地方定义方法,而是直接把方法写在实例化代码中
DelNumber delNumber2 = delegate (string str)
{
return str;
};
Console.WriteLine(delNumber2("这是匿名方法调用"));
Console.ReadKey();
}
#endregion
通过以上简单的示例看出:
匿名方法的语法:关键字delegate {参数列表}{语句块}
delegte { Paramters} {ImplementationCode}
delegate (string str)
{
return str;
};
使用的格式是:
委托类名 委托实例名 = delegate (args) {方法体代码} ;
delegate string DelNumber(string str); //委托类型的返回类型
//匿名方法 ,未在别的地方定义方法,而是直接把方法写在实例化代码中
DelNumber delNumber2 = delegate (string str)
{
return str; //根据返回类型,返回一个string类型
};
这样就可以直接把方法写在实例化代码中,不必在另一个地方定义方法。当然,匿名委托不适合需要采用多个方法的委托的定义。需要说明的是,匿名方法并不是真的“没有名字”的,而是编译器为我们自动取一个名字。
可以在以下地方使用匿名方法:
- 声明委托变量时为初始化表达式。
- 组合委托时在赋值语句的右边。
- 为委托增加事件时在赋值语句的右边。
6.Func 和 Action 委托
在之前,我们在使用委托的时候,都是自定义一个委托类型,再使用这个自定定义的委托定义一个委托字段或变量。而在后续的编程语言中又新加入了一种特性,C#语言预先为我们定义了两个常用的委托,一个是Func,一个是Action,还带来了Lambda,这使得委托的定义和使用变得简单起来, 在以后进行C#程序编写中引入委托更加灵活。
Action
C#中与预定义了一个委托类型Action,基本特点就是可以执行一个没有返回值,没有参数的方法。是一类没有输出参数的委托,但是输入参数可以为C#中的任意类型,即可以进行委托执行形式的方法。
static void printString()
{
Console.WriteLine("Hello World");
}
static void printNumber(int x)
{
Console.WriteLine(x);
}
static void Main(String[] args)
{
//Action基本使用
Action a = printString;
a(); // 输出结果 Hello World
//Action指向有参数的方法
Action<int> b = printNumber; // 定义一个指向 形参为int的函C#数
b(5); // 输出结果 5
}
Action可以通过泛型来指定,指向的方法有 0 - 16个参数
Action<int, int, string, bool 等等>
Func
Func同样也是预定的委托,是一种由返回值的委托,传递0-16个参数,其中输入参数和返回值都用泛型表示。
static int GetNumber()
{
return 1;
}
static int GetNumber(string str)
{
return 1;
}
static void Main(string[] args)
{
Func<int> a = GetNumber; // 定义一个Func 委托, 指向一个返回int类型的 方法
Console.WriteLine(a());
Func<string, int> b = GetNumber; // 泛型中最后一个参数表示返回值类型。
Console.WriteLine(b("Hello"));
}
注意:Func<string, int> 最后一个参数表示返回值类型,前面的都是形参类型。
7. Lambda表达式
江山代有才人出,纵然匿名方法使用很方便,可惜她很快就成了过气网红,没能领多长时间的风骚。如今已经很少见到了,因为delegate关键字限制了她用途的扩展。自从C# 3.0开始,她就被Lambda表达式取代,而且Lambda表达式用起来更简单。Lambda表达式本质上是改进的匿名方法。
在匿名方法中,delegate关键字有点多余,因为编译器已知将我们的方法赋值给委托。因此,我们很容易的将匿名方法的步骤转换为Lambda表达式:1. 删除delegate关键字。2.在参数列表和匿名方法主体之间放lambda运算符=>。
DelNumber delNumber2 = delegate (string str){ return str;}; //匿名方法
DelNumber delNumber2 = (string str) =>{ return str;}; //Lambda方法
Lambda表达式的灵感来源于数学中的Lambda积分函数表达式,例如下图:
Lambda表达式把其中的箭头用 => 符号表示。
上面的对比例子中,Lambda还可以进一步简化
delegate string DelNumber(string str); //委托类型的返回类型
DelNumber delNumber2 = (string str) =>{ return str;}; //Lambda方法
DelNumber delNumber3 = (str) =>{ return str;}; //省略类型参数
DelNumber delNumber4 = str =>{ return str;}; //省略类型参数( 如果只有一个隐式类型参数,可以省略周围的圆括号)
DelNumber delNumber5 = str => str; //语句块替换为return关键字后的表达式 ( 如果只有一个返回语句,可以将语句块替换为return关键字后的表达式)
如今Lambda表达式已经应用在很多地方了,例如方法体表达式(Expression-Bodied Methods)、自动只读属性表达式等等。
Lambda表达式形式上分为两种:
1.表达式Lambda
当匿名函数只有一行代码时,可采用这种形式。例如:
DelNumber delNumber= (s4, s5) => s4.Age <= s5.Age;
其中=>符号代表Lambda表达式,它的左侧是参数,右侧是要返回或执行的语句。参数要放在圆括号中,若只有一个参数,为了方便起见可省略圆括号。有多个参数或者没有参数时,不可省略圆括号。
相比匿名函数,在表达式Lambda中,方法体的花括号{}和return关键字被省略掉了。
用的也是表达式Lambda,这是Lambda表达式的推广, 是C# 6 编译器提供的一个语法糖。
2.语句Lambda
当匿名函数有多行代码时,只能采用语句Lambda。例如,上面的表达式Lambda可改写为语句Lambda:
DelNumber delNumber= (s4, s5) =>
{
//此处省略其他代码
return s4.Age <= s5.Age;
};
语句Lambda不可以省略{}和return语句。
完整示例
delegate string DelNumber(string str); //委托类型的返回类型
static void Main(string[] args)
{
DelNumber delNumber2 = (string str) => { return str; }; //Lambda方法
DelNumber delNumber3 = (str) => { return str; }; //省略类型参数
DelNumber delNumber4 = str => { return str; }; //省略类型参数( 如果只有一个隐式类型参数,可以省略周围的圆括号)
DelNumber delNumber5 = str => str; //语句块替换为return关键字后的表达式 ( 如果只有一个返回语句,可以将语句块替换为return关键字后的表达式)
Console.WriteLine(delNumber2("lambda"));
Console.WriteLine(delNumber3("lambda"));
Console.WriteLine(delNumber4("lambda"));
Console.WriteLine(delNumber5("lambda"));
Console.ReadKey();
}
注意:一个参数可以省略圆括号,多个参数必须圆括号,但是没有参数,必须使用一组空的圆括号
如: (参数,参数)=>{语句} 或者 表达式
(参数) =>{语句} 或者 表达式
参数 =>{语句} 或者 表达式
() =>{语句} 或者 表达式
总结
- 委托相当于用方法作为另一方法参数,同时,也可以实现在两个不能直接调用的方法中做桥梁,如在多线程中的跨线程的方法调用就得用委托。
- 熟悉在什么情况使用委托,在使用事件设计模式时,当需要封装静态方法时,当需要方便的组合时等多种情况下,可以加以使用。
- 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
- 在下一节中,将对事件进行简单介绍,并总结归纳。
参考 文档 《C#图解教程》
注:搜索关注公众号【DotNet技术谷】--回复【C#图解】,可获取 C#图解教程文件