从语言进化角度理解c#(委托篇)

发布时间:2023-03-02 13:00

c#1 是在2002年初的时候发布的,目前开发语言有很多,我个人还是觉得c++,java,c#算是老大哥级别,天下游戏一大抄,其实语言也是一样。虽然每个语言的设计者都不一样,但大家的核心思想还是比较接近的(大家都是互相抄的)

 

那么在c#1时代,最具代表性的几个语言特点是什么呢。我觉得有下面几个:

1:委托

2:属于c#的类型系统

3:值与引用,两种类型所涉及的托管与非托管机制

4:元表与反射

5:接口

 

 

不急不急,那我们来一个个聊,

 

先来聊聊第一个。委托是什么东西:

委托,一言以蔽之:

他是一个对象(好像没说一样)

是个什么对象呢:是一个可以同步又可以异步,而且内部会有一个调用链进行调用方法维护的,支持多路广播的,泛型的一个用于调用方法的对象 ……(好累)

 

那归根结底,他是用来调用方法的。那有人会问,我调个函数不就也一样调用方法了么,那差别就大了,首先我前面就说过了,天下语言一大抄,在设计之初,很多人觉得c#的委托不是和c++的函数指针差不多么,好的,如果你这么理解也没错(只是有点肤浅而已),他是一个提供了间接的方法,也就是说他不需要直接指定一个行为执行,而是封装这个行为包含在一个对象中,这个对象就像其它对象一样使用,而且这个对象可以封装,也可以看做一个方法的接口,将委托的实例看做实现了那个接口的一个对象。

 

有人说这话好难理解,那我来简单举个栗子你就明白了:

有一天阳光明媚,我带着我的狗出去散步,突然发现了一个漂亮MM,但是我这么高贵怎么可能亲自出马呢。于是!就“委托”了我的萌萌的大金毛去找MM要微信号(在它脖子上挂个牌子:好想认识你,请把微信号留给我吧),然后我的大金毛就聪明的过去啦。我想肯定比我亲自出去要成功率高吧,呀!刚才谁说的?谁说人不如狗的?

 

那我们来看看代码怎么写:

首先声明一个委托类型:

/* 委托类型这样: * 要所得一个字符串(这里就是MM的微信,当然以后也可能是别的,主要看委托谁)
 * 有一个形参与是字符串(这里是挂在狗脖子上的牌子上的字) */ 
delegate string getSomeThingWithMsg(string msg);

那下面是我的狗(对象)所包含的行为:

class Dog{ 
    //这里方法我就不做具体实现了 
    public AnimalSound Yap();//狗叫,返回一个AnimalSound的类(假定有这个类) 
    public void Sleep();//没有返回值的睡觉方法 
    public void Eat(Food food);//带有一个形参的进食方法 
    public string FoundSomething(string msg);//金毛巡回犬当然要找东西啦。msg是要找的东西,返回一个巡回的结果 
}

 

 

c#中委托实例需要一个与委托类格签名格式完全相同的方法,那这里我只能调用FoundSomething这个方法了。因为他符合委托签名的格式,你看是不是一摸一样?一个string返回值,一个string形参

 

那就开始委托工作啦:(这里先用C#1的写法,后面讲讲写法的进化)

Dog d = new Dog(); 
getSomeThingWithMsg getST; 
getST = new getSomeThingWithMsg(d.FoundSomething);//如果是静态方法直接指定类型名称就可以了 
string mm_wechar = getST("好想认识你,请把微信号留给我吧");//委托我的大金毛把MM微信要过来了

 

好啦。已经使用过委托这个功能啦,这里需要注意一点,委托实例的本身是不能被托管回收的,而且还会阻止它的目标被回收,这个就会内存泄露。

 

众所周知,C#语言进行最终编译执行是通过中间语言的(CIL),后面会谈谈反射,其实也是介于CIL中的元表和元数据实现的机制 ,这个中间层其实是可以反编译的然后我们来看看如果用反编译工具“ildasm.exe"会得到什么:

会生成一个派生于System.MulticastDelegate的密封类,这个密封类的基类是System.Delegate

那么就会在这个类中出现三个主要方法:

string Invoke(string msg); 
IAsyncResult BeginInvoke(string msg,AsyncCallback cb,object state); 
string EndInvoke(IAsyncResult result);

 

Invoke()其实就是真正执行的方法(方法的签名是不是一模一样),BeginInvoke()与EndInvoke()是异步,关于同异步,我想还是留到多线程的时候一起讲吧。现在只做一个简单的解释,代码执行会有一个线程调度“指针”,这个“指针”就是顺着你写的一行行代码一行行去执行的东西,异步好在你不用等这个“指针”执行完你的这个委托,直接往下走。(异步会再分出一个这样的执行代码指针到异步)

那么我们既然知道他们父类,为了好好研究下这个委托具体是个什么东西。我们去看看父类里的写法。

继承关系是这样的:

你的自定义委托函数=>System.MulticastDelegate=>System.Delegate=>System.Object

有人奇怪为什么会要连着继承两个基类呢。这个是历史原因导致的。回头我要不写个杂谈啥的聊一聊(还没开始就想了一堆要写的东西),还是先专注眼前吧。Object这个老祖宗我们就不看了。先来看除了Object外最上层的基类:Deletate

namespace System
{
    public abstract class Delegate : ICloneable, ISerializable
    {
        protected Delegate(object target, string method);
        protected Delegate(Type target, string method);

        public MethodInfo Method { get; }
        public object Target { get; }

        public static Delegate Combine(params Delegate[] delegates);
        public static Delegate Combine(Delegate a, Delegate b);
        public static Delegate CreateDelegate(Type type, Type target, string method, bool ignoreCase, bool throwOnBindFailure);
        public static Delegate CreateDelegate(Type type, Type target, string method);
        public static Delegate CreateDelegate(Type type, MethodInfo method, bool throwOnBindFailure);
        public static Delegate CreateDelegate(Type type, MethodInfo method);
        public static Delegate CreateDelegate(Type type, Type target, string method, bool ignoreCase);
        public static Delegate CreateDelegate(Type type, object target, string method, bool ignoreCase);
        public static Delegate CreateDelegate(Type type, object target, string method);
        public static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method, bool throwOnBindFailure);
        public static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method);
        public static Delegate CreateDelegate(Type type, object target, string method, bool ignoreCase, bool throwOnBindFailure);
        public static Delegate Remove(Delegate source, Delegate value);
        public static Delegate RemoveAll(Delegate source, Delegate value);
        public virtual object Clone();
        public object DynamicInvoke(params object[] args);
        public override bool Equals(object obj);
        public override int GetHashCode();
        public virtual Delegate[] GetInvocationList();
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context);
        protected virtual Delegate CombineImpl(Delegate d);
        protected virtual object DynamicInvokeImpl(object[] args);
        protected virtual MethodInfo GetMethodImpl();
        protected virtual Delegate RemoveImpl(Delegate d);

        public static bool operator ==(Delegate d1, Delegate d2);
        public static bool operator !=(Delegate d1, Delegate d2);
    }
}

看着密密麻麻的一堆,但实际我想让大家现在看的只有4个方法:

public static Delegate Combine(params Delegate[] delegates);
public static Delegate Combine(Delegate a, Delegate b);
public static Delegate Remove(Delegate source, Delegate value);
public static Delegate RemoveAll(Delegate source, Delegate value);

其实是3个(还有一个不是重载的么)那为什么要看这东西呢,因为引出一个委托比较重要的概念:委托链,其实面试的时候我问过很多程序员:他们都会回答是c++函数指针,但其实他们两者虽有相似之处,但还是有比较大的区别,区别在于:委托实际是一个对象,而函数指针就是一个指针,那么其实可以再引出一个问题,对象是包含一堆数据的集合,而指针就是一个指针而已,那委托对象里就有现在讨论的委托链。那有什么用呢?我来举个例子:比如现在突然发现我旁边另一个方向有一个MM,虽然现实中我想突然再养一只金毛比较麻烦,但程序中我想再生成一个狗的对象那是很容易的,所以我就又突然养了另一个金毛狗,我想同时让两只狗对向不同方向的两个MM要微信号。这时候我可以Combine的方法从对象的委托链中加一个委托,用Remove删除一个委托链,那我就可以更高概率拿到两个漂亮MM的微信号啦,想想就爽呀,c#抄了c++的重载操作符的功能(抄得好),这样我们还可以用+=的写法加入到委托链里啦,是不是很方便。

Dog d2 = new Dog(); 
getST += d2.FoundSomething; 

这里需要再说明一个概念:在这个程序里。我是发布命令的,c#语言叫“发布者”,那我的两条金毛狗,他们叫“订阅者”,什么你想叫他订阅狗也行吧。换句话说,只要有这种委托关系,那只要我想发布命令,两条狗就会随时随地为我服务。但这里要注意一个问题:你以为委托链真的只是一个链表么,其实不是,他的内部实现其实是一个类似于String.concat,也就是换句话说。你以为我用-=删除一个委托,返回过来的还是原来的实例吗?不是,已经是一个新的实例了,原来那个不受影响,就好像concat一样。

写到这我想让你想想有什么不对地方吗?有吗?如果你细心可能会发现一个问题:为什么我会写成getST += d2.FoundSomething;

之前第一条狗不是还写了new getSomeThingWithMsg(d.FoundSomething);吗,好的,那就继续引出新话题,委托的版本进化:

我之前说过了。我的派狗把妹的写法是基于c#1的,那第二条狗就用了c#2 的一种新式简单写法:这种写法专有名词叫:"隐式转换"

其实没什么花头,就是个语法糖而已,让你少打两个字,你再把它反编译,你发现其实CIL没啥变化。

c#2里其实对委托的该造还有不少,再介绍一个叫匿名方法,还拿我这个来举例子,我的第二条狗,其实也没啥必要再新创建个对象,你说他还有一些类方法也对我没啥用。我不想给他喂吃的也不想听他叫,那我就不生成对象了。我直接写一个匿名方法,一样来实现我的委托:

getST += delegate(string msg){ 
    string mm_wechar;
    ...//具体实现省了,反正得有个返回结果
    reutrn mm_wechar;
}

怎么样,是不是发现好像节省了不少内存与性能,我还不用管第二只狗对象的内存相关的东西。

但微软大大好像也不止于此,他们又想办法省了一些代码,反正参数你都定了。(编译通过说明你的签名已经被检查无误了)于是,他们竟然把delegate后面的参数都省了

 

getST += delegate{ 
    string mm_wechar;
    ...//具体实现省了,反正得有个返回结果
    reutrn mm_wechar;
}

这个叫使用匿名方法的简写(真是简写中的简写呀)

你是不是容易感觉自己好像代码的写法上有了不少长进,别急。告诉你个不好的消息。c#2还流行的东西,c#3里又发明了一个lambda表达式(其实也是抄的),虽然这种c#2的写法也还保留着,但其实大家都用lambda的写法啦

 

getST += (msg) =>
{
    string mm_wechar;
    //...具体实现
    return mm_wechar;           
};

是不是感觉写又少打了几个,毕竟delegate这几个字可以省了,但这个lambda也不是省油的灯,(比如在c#5中给lambda加了async 和 await 两个关键字,有了异步能力,c#7加入了元组内置支持)这些东西有空再写个深谈lambda的啥文章具体说吧。

c#2改动其实还是蛮大的。就委托来说。还有两个新加的概念非常重要,一个是泛型,还有一个是逆变与协变,但这两个东西其实也不是专门为委托专属的,要不还是专门写一个研究泛型和逆变协变来说吧

总结一个委托相关的进化新特性吧:

1:匿名方法及匿名方法的简写(c#2)

2:泛型(c#2)

3:逆变与逆变(c#2)

4:lambda表达式(c#3)

5:隐式转换或者也叫方法组转换(就那个前面叫你看第二条狗创建有啥问题的代码)

经过这么多改进,委托应该很完善了吧。但我告诉你,不完美,为啥?因为有BUG!!!

你觉得微软的东西也能有BUG,那你等我说完,委托这个东西其实有一些漏洞,主要是封闭会导致调用委托的程序不经意间会产生BUG,

比如:我在生成第二条狗的时候没有用+=,而是用了=号,会怎么样。那委托链就会生成一个新的,只含有第二条狗的委托对象给我,那我的第一条狗呢(其实还在内存中)但实际上我把委托对象覆盖了,我目前代码就调用不到了。这可不行。我这狗可养了4年多了!那是不是会产生BUG?这是其一,还有呢!

因为既然委托是对象,那么就存在一个封闭问题,如果我的委托对象是一个public的,那么就有可能被外部类篡改,那就有问题啦,别人还能对我的狗发号施令么?那美眉的微信不送到别人那去了吗?那也绝对不行!那怎么解决这两个问题呢?

正因为这些问题,我们引出的一个新的东西:”事件“

欲知后台如何,请听下回分解!

 

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号