【C#】委托/Lambda/事件

0 参考文章

知乎:事件是以特殊方式声明的委托字段吗
书籍:《C# 7.0本质论》

1 委托(delegate)

为了方便理解,我们先从委托的用途讲起。

1.1 委托的用途

在C/C++中,“函数指针”将对方法的引用作为实参传给另一个方法,而委托在C#中承担着相似的功能。虽然这样说,但我们对委托用途的理解可能还是很抽象,这里用一个简单的例子帮助理解。

冒泡排序是最基础的排序算法,它的代码大致如下:

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;
            }
        }
    }
}

该方法对整数数组执行升序排序。

但为了能够选择升序和降序,我们开始拓展这段代码。
第一个方案:拷贝以上代码,然后把大于操作符换成小于操作符。
第二个方案:增加一个参数,指出我们当前希望如何排序,然后在代码里进行判断。

但以上代码只照顾到了两种可能的排序方式,如果还想要按其他方式进行排序,代码就会变得很庞大。

为了增加灵活性,减少重复代码,我们可以将比较方法作为参数传入。而此时我们需要有一个数据类型可以表示方法,这就是委托。

委托加入后,代码会变成这样:

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,并且指出委托的返回值和所需参数,只有方法的返回值和参数与委托一致,才可以将方法传递给委托。

//先定义委托
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
代表有返回值的方法。

delegate TResult Func<参数,参数...,out TResult>

Action
代表无返回值的方法。

delegate void Action<参数,参数...>

PS:虽然C#开始提供Func和Action委托,减去了自定义委托类型的必要(要写声明还要自定义名字),但有时候出于可读性,还是可以考虑声明自己的委托类型。如Comparer委托就能使人对其用途一目了然。

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等是对数据类型的定义,那么委托就类似于对“方法类型”的定义(看着下面的代码仔细琢磨)。

string str;
delegate void Method(int num);  //理解:Method是变量名,delegate/返回值/参数共同构成了一种“方法类型”

2 Lambda表达式

上文提到的冒泡排序,如果我们想去调用它,代码大致如下:

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表达式),简化了这个过程。

BubbleSort(items,(first,second)=>{return first>second});

3 事件(event)

事件就是对委托的封装,相对于委托,它只提供了“+=”和“-=”两个方法,保证了在外部操作时的安全性。

对订阅的封装
只提供“+=”和“-=”,避免程序员在编写代码时错误地使用“=”代替“+=”。

对发布的封装
不再提供Invoke方法,保证只有指定字段发生变更时,委托方法才会被调用,避免外部主动调用。

private delegate void Method();
public event Method EventName;   //提供给外部
  • 16
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值