.net 事件委托 java_如何通俗易懂的理解c#中的委托和事件,另外,.net和c#有哪些具体的关系?...

首先,委托是什么?

委托是一种类型。需要注意,delegate是C#中一个关键字,用于声明委托类型。而System.Delegate是.NET基础类库中的一个类型,它是所有委托类型的基类,但是不允许显式地从这个类派生新类。

为什么说是一种类型,因为每次我们在使用delegate关键字时,我们都是在定义一个新的类型,而这个类型是Delegate的子类,我们说委托类型时,实际上是指的所有使用delegate定义和派生自Delegate的类型。尝试从Delegate类型派生新类会发生错误

基础类库中的Action实际上就是一个委托类型,用于引用最高可以带16个参数的、不返回值的方法。同样的Func类型也是委托类型,和Action不同的是,Func引用的是有返回值的方法。

那么,委托可以干什么?

委托可以引用方法,并且可以通过委托来调用其他方法。很多书、文档、教程经常会说这样的一句话:“委托和C/C++中的函数指针类似,但是是类型安全的”。函数指针只是函数所在的内存地址,通过函数指针可以引用函数。在C#中使用委托类型的实例引用方法,前面提到过,委托是一个类,那么它也有对应的方法和属性,我们可以使用委托类型的实例更加灵活地调用方法。

那么所谓的“委托是类型安全的”又是如何体现的呢。如果我们声明了如下的委托类型:

public delegate string MethodReturnString();

我们期望通过这个委托类型去引用”不需要参数,返回string“的方法。

我们再声明另一个委托

public delegate string FunctionReturnString();

此时我们定义一个方法:

public static string SayHello()

很显然,这个方法的签名对于两个委托类型来说都是兼容的,我们可以:

MethodReturnString methodReturnString = SayHello;

FunctionReturnString functionReturnString = SayHello;

但是即便如此,尽管上面声明的两种委托可以引用的方法的签名是相互兼容的,但是仍然是两种不同的类型,因此我们不能像这样:

methodReturnString = functionReturnString;

而如果使用C++中的函数指针,我们也可以去引用和指针类型匹配的函数:

typedef string (*FunctionReturnString)();

typedef string (*MethodReturnString)();

string SayHello()

{

return string("Hello");

}

int main(int argc, char const *argv[])

{

FunctionReturnString funcRetStr = &SayHello;

MethodReturnString methodRetStr = funcRetStr;

return 0;

}

在C++中这样完全是OK的。当然这里只是通过typedef去声明了两个类型别名,不过看起来会和上面的C#代码十分相似。实际上并没有产生新的类型。这两个类型别名实际上是指向相同的类型。函数指针同样是指针,使用时并没有产生新的类型,因此实际上类型相同的函数指针都可以引用对应类型的函数。但C#定义委托时,是定义了一个新的类型,就算两个委托类型引用签名相同的函数,他们的实例仍然属于不同类型。

而C#中的委托还有一个更重要的特性是,委托可以引用多个方法进行多播。而函数指针只能指向某一个函数。委托对象的GetInvocationList()方法就会返回一个委托对象引用的所有方法的列表。C#中给了委托一些语语法糖,比如+=和-=用于给委托的InvocationList增减对方法的引用,而且可以使用调用函数的括号操作符调用委托,实际上也可以显式调用委托的Invoke方法来调用。

因此常说的“C#的委托类似于C++中的函数指针”实际上更多的是说的功能上(用于引用函数)的相似之处,本质上来说有不少区别,当然你完全可以在C++中使用仿函数来更好地模拟C#的委托。

经评论区热心网友提醒,需要注意的是,调用委托时,应当进行null检查。因为问题是委托和事件的区别,所以我认为题主是在知道委托和事件的用法的基础上提问的,也就没有细说基本用法上面的事情。

因为我们可以动态地为委托绑定和解绑方法,所以在我们调用的时候不确定这个委托是否还有绑定的方法。调用一个没有绑定方法的委托会抛出NullReference异常

最直白的写法就是用if检查,不过在C#6及以上的版本中,可以使用null-conditional operator进行null检查,并且也提倡对委托使用类似这样的写法:

action?.Invoke();

我们又为什么需要用委托去引用方法?

委托对象实际上是方法的包装器,它把方法包装成一个对象。这意味着,我们可以将方法通过委托对象引用以便操作,也可以作为参数传递给其他方法,比如为一个方法传递一个委托作为回调函数,实际上lambda表达式也产生委托。并且在设计上委托还有利于系统不同部分的逻辑分离,降低耦合程度。

举个例子,侦察兵侦察敌情。其他兵种和指挥部需要了解敌情。

当侦察兵发现敌情时,需要通知其他人。其他人做出响应,比如坦克开始行进,狙击手开始瞄准敌人,总部开始部署新的人员,但是其他人并不知道敌人什么时候来,敌人在哪儿,所以我们需要侦察兵告诉他们。

当然,我们可以让侦察兵挨个通知,告诉后面的坦克部队、狙击手、总部敌人来了。

但是,我们的侦察兵不是指挥官,他不知道到底有多少人需要知道这次发现的情报,万一少通知了几个人呢?而且,他也不知道应该让他们具体做什么。

因此他们用上了无线电。侦察兵记下了需要了解敌情的部门的无线电频道,侦察兵一旦发现敌情,就发电通知。而当这些需要情报的部门收到通知时,就能够对侦察兵提供的情报,根据自己的情况做出响应。而涉及到null检查,对于一个侦察兵来说,需要知道谁需要报告敌情,他才能去报告。

委托把方法具体的实现交给了其他类的方法,我们在声明这个委托对象的类中只需要调用。

和上述例子相对应,我们编写代码时并不知道某个事件何时发生,而触发事件的对象也不知道其他哪些对象对这个事件感兴趣,在发生时又该做什么,所以说主动调用其他对象的方法是不可取的。C#中提供的委托和事件非常适合实现基于“订阅和发布”的观察者模式。

接下来我们引入事件,并分析事件和委托的关系。

首先,事件就是一个特殊的委托类型的字段。(事实上也可以声明属性风格的事件以便在订阅和发布事件时进行额外的工作,这里为了简单起见暂时只说明直接使用字段风格的事件)使用event关键字定义事件,event关键字后面只能是委托类型。

我们通过事件来演示上面的侦察兵的例子,省略一些方法的具体实现

class Recon

{

public delegate void EnemySpottedEventHandler(string message, Enemy enemy);

public event EnemySpottedEventHandler EnemySpotted;

public void Report(Enemy enemy)

{

EnemySpotted?.Invoke("Attention! Enemy spotted!", enemy);

}

}

class Sniper

{

private void ListenToRecon(Recon recon)

{

recon.EnemySpotted += OnEnemySpotted;

}

private void OnEnemySpotted(string message, Enemy enemy)

{

AimAt(enemy);

}

}

在侦察兵类中,定义了一个委托,这个委托引用需要两个参数的方法。第一个是侦察兵传递的消息,第二个是敌人的情况。

随后定义了一个事件,是EnemySpottedEventHandler类型的事件,当然其本质就是一个委托字段。

侦察兵发现敌人执行Report方法,这里我们发布了EnemySpotted事件,前面说过,事件本质就是一个委托字段,我们按照调用委托的方法发布了事件。

狙击手的定义中,我们定义了听取侦察兵消息的方法,在这里我们订阅了侦察兵的EnemySpotted事件,再次强调,因为事件就是一个委托,我们使用和委托一样的+=操作符为其增加了对狙击手的OnEnemySpotted方法的引用。

就这样,在每次侦察兵调用Report方法,发布EnemySpotted事件时,所有引用的方法都会被顺次调用,所有听取这个侦察兵消息的部门都会在听到报告时调用自己注册的方法。

就这样我们实现了方法的调用和实现的逻辑分离。

说到这里,你会说我一直强调事件本质就是一个委托字段,那么为什么不直接用委托还要加个事件呢。

我们不能让别人用侦察兵的无线电通知其他部门,万一谎报军情怎么办?我们必须规定只有侦察兵自己能够报告敌情。

这就是我们为什么要使用事件。事件只能在声明它的类中被触发(调用),在声明它的类之外只能订阅和取消订阅(增加和删除对方法的引用)。尝试在狙击手类中触发事件,会提示错误

最后,关于.NET和C#的关系我觉得可以另外开一个问题。

这个问题其实可以延伸到.NET是什么。首先.NET是一个开发平台,它不是一门编程语言,也不是一个类库,更不是C#。.NET为具体的实现包含了一系列标准,包括具体的编程语言、具体的运行时环境、对于.NET Standard和其他类库的实现等等,而C#是这一些列要求中编程语言的一种。

和Java的体系来对比,当笼统地说.NET时对应的就是Java平台而不是Java语言本身。而如果说的是.NET Framework或者.NET Core这种具体的.NET实现就就应当和Java SE、Java EE等名词对应。而C#语言对应的显然就是语言层面的Java语言。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值