5

重学c#系列——委托和匿名函数[二十五] - 敖毛毛

 1 year ago
source link: https://www.cnblogs.com/aoximin/p/16922423.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

重学c#系列——委托和匿名函数[二十五]

简单介绍一下什么是委托。

以前也写过委托,这次算是重新归档,和新的补充吧。

https://www.cnblogs.com/aoximin/p/13940125.html

有些人说委托是函数指针的包装,也有些人说委托是一个方法或多个方法的引用。

这都是没有问题,委托是一个概念,微软官方文档说委托是一种引用类型,表示对具有特定参数列表和返回类型的方法引用。

我觉得太啰嗦了,实际上就是方法的引用。

上面都是委托的概念,但是实现方式每种语言可能都不一样。

比如c++ 和 c 用的是函数指针,而c# 用的是生成包装类(等下IL),当然本质还是函数指针。

那么来看下委托。

internal class Program
{
	delegate int TestDelegate(int a);

	static void Main(string[] args)
	{
		TestDelegate a = test;
		a(0);
	}

	public static int test(int a)
	{
		return 0;
	}
}

将test 给了委托a,然后调用的时候直接a()就可以了。

用起来十分简单。

实际上对IL来说其实是没有委托这个概念的,通过反编译来看下原理。

private static void Main(string[] args)
{
	TestDelegate a = new TestDelegate(test);
	a(0);
}

实际上会生成TestDelegate这样一个类,然后将test 引用添加进去。

来看下il。

1289794-20221124161028675-1707402722.png

再看下TestDelegate是一个什么样的类。

1289794-20221124161140090-371491323.png

就是把调用的object 和 方法的引用放入包装类中了,然后invoke 可以进行调用。

如果是多个方法的引用呢?

internal class Program
{
	delegate int TestDelegate(int a);

	static void Main(string[] args)
	{
		TestDelegate a = test;
		a += test;
		a(0);
	}

	public static int test(int a)
	{
		return 0;
	}
}

看下IL:

1289794-20221124161906108-1275866209.png

原理就是又new了一个TestDelegate,然后用Delegate 将两个相连。

Combine 是一个静态方法哈。

1289794-20221124162133774-1177214787.png

本质是调用a的combineImp这个方法。之所以有这个一个静态方法是为了避免出现a为空的情况,如果a为空,直接把b给a啊。

这个是我们写链式结构可以学习的,这样就不用判断声明的时候是否为空。

然后c# 帮我们提取定义了很多委托,以至于我们几乎不用去声明委托。

比如Func 和 Action,Func 有返回值,Action没有。

1289794-20221124162718336-1783167781.png

下面介绍匿名函数,匿名函数有两个要介绍的,他们分别是匿名方法和lambda表达式。

他们原理都一样,都是生成匿名函数,只是写法不一样。

delegate int TestDelegate(int a);

static void Main(string[] args)
{
	TestDelegate a = delegate (int a)
   {
	   return 0;
   };
}

看下反编译后的内容。

private static void Main(string[] args)
{
	TestDelegate a = <>c.<>9__1_0 ?? (<>c.<>9__1_0 = new TestDelegate(<>c.<>9.<Main>b__1_0));
}

那么看下<>c 这个类:

1289794-20221124163443420-730238650.png

首先看到第一个框,那么作者的意思是想把 <>c做成一个单例。

里面有委托的引用。然后下面这个

b__1_0 就是生成的方法。

其实匿名方法还是编译帮忙生成对应的方法名。

如果用lambda 表达式写的话,那么是这样写的:

1289794-20221124164026464-180098822.png

这种写法编译出来的代码一模一样。只是不同写法的问题。

值得注意的是匿名函数如果引用了外部的信息,那么会形成闭包。

static void Main(string[] args)
{
	Student s = new Student();
	TestDelegate a = (a) => {
		s = null;

		return 0;
	};

	a += (b) => {
		return 0;
	};

	a += (c) => {
		return 0;
	};
}

首先b和c(第二个和第三个匿名)没有引用外部对象,那么都会生成在<>c这个类中。

第一个有外部引用生成了另外一个类。

1289794-20221124164726177-1350433468.png

然后实例化<>c__DisplayClass1_0后,那么会将s赋值进来。

1289794-20221124164915348-1791144115.png

所以会形成这种闭包,这是值得注意的地方。

下一节委托的发布订阅与事件。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK