目录
- [0 参考文章](#0 参考文章)
- [1 委托(delegate)](#1 委托(delegate))
-
- [1.1 委托的用途](#1.1 委托的用途)
- [1.2 委托的声明](#1.2 委托的声明)
-
- [1.2.1 自定义委托](#1.2.1 自定义委托)
- [1.2.2 System.Func和System.Action](#1.2.2 System.Func和System.Action)
- [1.3 委托是一种特殊的类](#1.3 委托是一种特殊的类)
- [2 Lambda表达式](#2 Lambda表达式)
- [3 事件(event)](#3 事件(event))
0 参考文章
书籍:《C# 7.0本质论》
1 委托(delegate)
为了方便理解,我们先从委托的用途讲起。
1.1 委托的用途
在C/C++中,"函数指针"将对方法的引用作为实参传给另一个方法,而委托在C#中承担着相似的功能。虽然这样说,但我们对委托用途的理解可能还是很抽象,这里用一个简单的例子帮助理解。
冒泡排序是最基础的排序算法,它的代码大致如下:
csharp
public static void BubbleSort(int[] items) {
if (items == null) return;
for(var i = items.Length - 1; i >= 0; i--)
{
for(var j = 0; j+1 <= i; j++)
{
if (items[j] > items[j + 1])
{
var temp = items[j];
items[j] = items[j + 1];
items[j + 1] = temp;
}
}
}
}
该方法对整数数组执行升序排序。
但为了能够选择升序和降序,我们开始拓展这段代码。
第一个方案:拷贝以上代码,然后把大于操作符换成小于操作符。
第二个方案:增加一个参数,指出我们当前希望如何排序,然后在代码里进行判断。
但以上代码只照顾到了两种可能的排序方式,如果还想要按其他方式进行排序,代码就会变得很庞大。
为了增加灵活性,减少重复代码,我们可以将比较方法作为参数传入。而此时我们需要有一个数据类型可以表示方法,这就是委托。
委托加入后,代码会变成这样:
csharp
public static void BubbleSort(int[] items,Func<int,int,bool> compare) {
if (items == null) return;
if (compare == null) return;
for(var i = items.Length - 1; i >= 0; i--)
{
for(var j = 0; j+1 <= i; j++)
{
if (compare(items[j], items[j+1]))
{
var temp = items[j];
items[j] = items[j + 1];
items[j + 1] = temp;
}
}
}
}
显然灵活多了。
PS:写到这里我突然理解了Sort((x,y)=>x>y)的含义,之前对升序到底对应x>y还是y>x总是一知半解。x>y那么x和y就交换,所以就是升序排列(虽然Sort底层是优化过的快排,不是冒泡排序,但也有两个数比对的步骤,所以代入一下就能得出结论)。
1.2 委托的声明
1.2.1 自定义委托
声明委托需要使用关键词delegate,并且指出委托的返回值和所需参数,只有方法的返回值和参数与委托一致,才可以将方法传递给委托。
csharp
//先定义委托
delegate void Feedback();
//然后使用new操作符构造委托实例并传入方法
class Program{
static void Main(string[] args){
//向委托的构造函数传递静态方法
Feedback fbStatic = new Feedback(Program.FeedbackToConsole);
//传递实例方法
Feedback fbInstance = new Feedback(new Program().FeedbackToFile);
//调用委托的两种不同方式
fbStatic.Invoke();
fbInstance();
}
}
1.2.2 System.Func和System.Action
为了减少定义自己的委托类型的必要,C#推出了一组常规用途的委托。
Func
代表有返回值的方法。
csharp
delegate TResult Func<参数,参数...,out TResult>
Action
代表无返回值的方法。
csharp
delegate void Action<参数,参数...>
PS:虽然C#开始提供Func和Action委托,减去了自定义委托类型的必要(要写声明还要自定义名字),但有时候出于可读性,还是可以考虑声明自己的委托类型。如Comparer委托就能使人对其用途一目了然。
csharp
public delegate bool Comparer(int first,int second);
class Program{
public static void BubbleSort(int[] items,Comparer comparer){}
}
1.3 委托是一种特殊的类
委托实际上是特殊的类,它派生自System.MulticastDelegate,而后者又派生自System.Delegate,其实把它理解成一种C#里的类型就可以了,但它和一般的类型又有些不同。如果说int,string等是对数据类型的定义,那么委托就类似于对"方法类型"的定义(看着下面的代码仔细琢磨)。
csharp
string str;
delegate void Method(int num); //理解:Method是变量名,delegate/返回值/参数共同构成了一种"方法类型"
2 Lambda表达式
上文提到的冒泡排序,如果我们想去调用它,代码大致如下:
csharp
public static void BubbleSort(int[] items,Func<int,int,bool> comparer){}
//声明方法
public static void GreaterThan(int first,int second){
return first>second;
}
//Main函数里调用
static void Main(string[] arg){
int[] items = new int[5];
//...初始化
BubbleSort(items,GreaterThan);
}
你会发现整个过程下来要进行的准备有点太过复杂了,其实我们只需要主体的return first>second。于是C#提供了匿名函数(C# 3.0叫Lambda表达式),简化了这个过程。
csharp
BubbleSort(items,(first,second)=>{return first>second});
3 事件(event)
事件就是对委托的封装,相对于委托,它只提供了"+="和"-="两个方法,保证了在外部操作时的安全性。
对订阅的封装
只提供"+="和"-=",避免程序员在编写代码时错误地使用"="代替"+="。
对发布的封装
不再提供Invoke方法,保证只有指定字段发生变更时,委托方法才会被调用,避免外部主动调用。
csharp
private delegate void Method();
public event Method EventName; //提供给外部