微软.NET6开发的C#特性——委托和事件

我是荔园微风,作为一名在IT界整整25年的老兵,看到不少初学者在学习编程语言的过程中如此的痛苦,我决定做点什么,下面我就重点讲讲微软.NET6开发人员需要知道的C#特性,然后比较其他各种语言进行认识。

C#经历了多年发展, 进行了多次重大创新, 大幅优化了开发者的编码体验。在.NET 平台移交给.NET基金会运营后, C#更新的越来越不像原来的C#了,但总体上来说,所有改进依然以优化开发者的编码体验为最终目的。

首先,要记住一张表,如下:

C#版本     发布时间         .NET版本                       VS版本                  CLR版本

C#1.0        2002-2           .NET Framework 1.0     VS.NET 2002      .NET Framework CLR 1.0

C#2.0        2005-11         .NET Framework 2.0     VS2005               .NET Framework CLR 2.0

C#3.0        2006-11         .NET Framework 3.0     VS2008               .NET Framework CLR 2.0 

C#3.0       2007-11          .NET Framework 3.5     VS2008                .NET Framework CLR 2.0 

C#4.0       2010-4           .NET Framework 4.0     VS2010                .NET Framework CLR 4.0

C#5.0       2012-2           .NET Framework 4.5     VS2012               .NET Framework CLR 4.0

C#6.0       2015-7           .NET Framework 4.6      VS2015              .NET Framework CLR 4.0

C#7.0       2016-8           .NET Framework 4.6.2    VS2017(v15)     .NET Framework CLR 4.0

C#7.1       2017-4           .NET Framework 4.7      VS2017(v15.3)   .NET Framework CLR 4.0

C#7.2       2017-10         .NET Framework 4.7.1   VS2017(v15.5)    .NET Framework CLR 4.0

C#7.3       2018-4           .NET Framework 4.7.2   VS2017(v15.8)   .NET Framework CLR 4.0

C#8.0       2019-4           .NET Framework 4.8    VS2019(v16.3)    .NET Framework CLR 4.0

C#8.0       2019-9           .NETCore 3.0                 VS2019(v16.4)    .NETCore CLR 3.0       

C#9.0       2020-11          .NET 5.0                        VS2019(v16.8)    .NET CLR 5.0           

C#10.0     2021-11          .NET 6.0                        VS2022(v17)       .NET CLR 6.0 

看完这张表,我真的是很感慨,从测试版开始,我居然陪伴着.NET和C#走过了二十多年,我不知道有没有微软公司的人在看这篇文章,如果有的话,不知道我这样的二十多年的.NET和C#程序员有没有机会去微软中国和微软亚洲研究院的总部去参观一下,去坐一坐,并作一下技术交流。二十多年了,人生又有几个二十多年啊。

.NET平台是基于IL中间语言的应用运行环境,面向对象语言C#是平台的主要开发语言。除此之外还有同样面向对象的C++/CLI。C++/CLI主要用于和原生C++交互,在.NET平台中仅支持Windows系统。

C#和.NET平台本来是微软为了与Java平台竞争而打造的,C#在设计时充分总结了Java的经验教训,解决了大量Java的基本设计缺陷。本着为一线开发者谋实惠的宗旨,C#设计了大量能减轻开发者的编写负担、容易理解且安全高效的实用功能。为了尽可能降低因安全措施导致性能大幅下降的影响,C#还在有限的情况下保留了C/C++语言的部分语法和功能。到了.NET时代,微软依然在运行时(Runtime)和语言两边同时进行着优化。

随着上世纪九十年代Java的发布,软件公司和开发者开始感受到基于虚拟机的托管语言所带来的好处,微软也不甘示弱,在2001年发布了.NET Framework平台和C#。提供了完整的基础面向对象支持。

委托就是为了取代switch/ case,当我们有很多参数,需要传给不同的函数执行出不同的结果的时候。我们就可以建立委托函数。

委托函数就像一个类,这个类声明了一种能够装下普通参数(另一个子函数)的大函数。能够把其他函数和其他函数对应的形参作为自己的形参来传递。
 
委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易。

我们现在对委托做一个总结:委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。

使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。

事件和委托的区别

事件是基于委托的,为委托提供了一个发布/订阅机制。可以说事件是一种特殊的委托,他的调用和委托是一样的。

事件的声明
public event 委托类型 事件名称

通常事件的命名以事件名称+Event来命名。如public event delegate NotifyEvent;

事件和委托的区别如下:

事件只能在方法的外部进行声明,而委在方法的外部和内部都可以声明。

事件只能在类的内部触发,不能在类的外部触发。而委托在类的内部和外都都可以触发。

委托一般用于回调,而事件用于外部接口。例如在观察者模式中,在被观察者中可以声明一个事件作为外部观察者注册的接口。

同时,这个事件只能在被观察者内部触发,而观察者中无法触发该事件,从而保证了安全性。

委托和事件的定义 


委托

在这里再说一遍委托跟事件的简单定义

委托:delegate 是一种可用于封装命名或匿名方法的引用类型。 委托类似于 C++ 中的函数指针;但是,委托是类型安全和可靠的。

委托是一种动态调用方法的类型,属于引用型。

委托是对方法的抽象和封装。委托对象实质上代表了方法的引用(即内存地址)

委托允许将方法作为参数进行传递。

委托可用于定义回调方法。

委托可以把多个方法链接在一起。这样,在事件触发时可同时启动多个事件处理程序。

委托签名不需要与方法精确匹配。

事件

事件:事件是特殊类型的多路广播委托,仅可从声明它们的类或结构(发行者类)中调用。

如果其他类或结构订阅了该事件,则当发行者类引发该事件时,会调用其事件处理程序方法。

事件表示C#中已定义的一个对象,即处理通知过程的对象

通常,每个事件的发生都会产生发送方和接收方。在.net框架中,事件是将事件发送者(触发事件的对象)与事件接受者(处理事件的方法)相关联的一种代理类,即事件机制是通过代理类来实现的。当一个事件被触发时,由该事件的代理来通知(调用)处理该事件的相应方法

简单说了一下两者的定义,眼神好的小伙伴这时候就会发现,事件的说明里有提到一句话:“事件也可以算一种特殊的委托”,这句话不是特别准确,但是也不妨可以这样理解。

正因为如此,所以要我们先了解了 委托 再来看 事件 ,容易混淆大家的地方大多在于这两者之间的区别,下面说一下委托和事件之间的区别:

委托和事件的区别如下:


举例说明

我们将创建两个类ClassA和ClassB,这两个类都很简单,只有一个方法,并且这两个类的方法签名一样。ClassC内有委托和事件,为了演示方便,我们将委托和事件的访问权限都设为public。下面我们将主要看看委托和事件在使用上面的区别。

Class1

public class Class1
{
    //声明委托
    public delegate void NumberChanger(int n);
    //声明事件
    public static event NumberChanger changer;

    public static void test()
    {
        changer(100);
    }
    static void Main(string[] args)
    {
        Class1.changer += Class2.ClassA_Test;
        Class1.changer += Class3.ClassA_Test;
        Class1.test();
    }
}


Class2

    class Class2
    {
        public static void ClassA_Test(int i)
        {
            Console.WriteLine("Class2:"+i);
        }
    }


Class3

    class Class3
    {
        public static void ClassA_Test(int i)
        {
            Console.WriteLine("Class3:"+i);
        }
    }


区别一:是否是一个类型

 

  class Class3
    {
    static void Main(string[] args)
    {
        //委托正确使用
        Class1.NumberChanger n1 = Class2.ClassA_Test;
        //事件使用 编译器报错
        Class1.changer handle2 = Class2.ClassA_Test;
    }


错误:Class1.changer 是“字段”,但此处被当做“类型”来使用

区别二:委托可以在声明它的类外部进行调用,而事件只能在类的内部进行调用。

(1)在类外部调用委托

(2)在类外部调用事件

事件“ClassC.Say_EventHandler”只能出现在 += 或 -= 的左边(从类型“ClassC”中使用时除外)

区别三:委托可以在外部类使用 = 来赋值,事件只能在内部类用 = 赋值,外部类不可以

从编译器提示的错误,我们可以了解到,事件只能在声明它的类内部被调用。从事件本身来讲,事件一般用于类自身的属性变化时,用来通知外界自身的变化的。我们将对ClassC内部的一个属性赋值,然后调用事件,模拟对外通知。代码如下所示

总结 

事件的使用方式跟委托没什么区别,事件就相当于一个加了"event"修饰符的委托,在加了这个修饰以后,委托的部分功能就相当于被阉割了。通过这个加了修饰符以后,我们可以更好的控制注册和注销,对于一个外部类来说,它就只能通过"+=/-="注册自己和注销自己,而且外界也不能主动触发一个事件。

委托一般用于回调,而事件用于外部接口。例如在观察者模式中,在被观察者中可以声明一个事件作为外部观察者注册的接口。

同时,这个事件只能在被观察者内部触发,而观察者中无法触发该事件,从而保证了安全性。

事件与委托最主要的区别应该是不能在外部调用,但可以通过+=或-=进行注册,但如果委托变量为私有,则外部不能注册;如果为公有,则外部有可以调用,破坏了封装,所以没办法,在这种情况就定义一个event就好了

委托

委托可以说是C#的一个强大特性。委托这个概念其实是来源于C语言的函数指针,又在面向对象的方面里完成了升级。C语言的函数指针能完成各种非常有效的功能,最典型的就是回调函数,可以说事件驱动的图形界面编程的基石就是函数指针。但是C语言的函数指针可能会导致各种bug,甚至黑客入侵时很多时候也是瞄着函数指针下手。

因为C语言从不验证函数指针所指向的函数。C#开发团队深知函数指针的重要性,必须想办法控制住函数指针,否则后患无穷,因此最终诞生了委托。

委托本身也是一种类型,但是和结构体一样,有专门的定义语法。在方法声明头部的返回类型之前加上访问修饰和delegate关键字就能完成委托类型的定义。委托类型的所有成员都统一由编译器生成。委托是面向对象的函数指针,可以同时保持对方法和目标对象的引用。同时委托又是强类型的,会严格验证所引用的方法签名和委托支持的签名是否兼容。

微软又设计了多播委托,可以让一个委托对象同时引用多个方法,调用委托就能同时调用引用的所有方法,如果多播委托有返回值,只有最后一个委托的返回值有效。实际上所有自定义委托类都隐式派生自多播委托类,继承路径为: System.Object→System.Delegate→System.MulticastDelegate→各种自定义委托

而Java在面对函数指针时选择了删除函数指针。但这个功能本身是非常有必要的,怎么办呢?最后Java选择了用接口去模拟函数指针的功能,并取名叫函数式接口。但是接口不是专门为函数指针设计的,接口的“兼取”导致了函数指针的语义被接口掩盖了。

一个合格的函数式接口只能声明一个方法,但只声明了一个方法的接口却不一定是函数式接口。接口的冗长语法也让实现函数指针的功能变得非常麻烦,这一点稍微对比一下WinForm和安卓的事件处理器的模板代码就能看出来。

虽然后来Java8增加了Lambda表达式和方法引用功能,在一定程度上对代码有所简化,但C#在更早之前就增加了Lambda表达式功能并进行了持续更新。

C#、C和Java的委托定义语法的示例代码如下所示。

(1)C#

namespace Example
{
    public delegate string MyDelegate(string str1, string str2);

  class Program
  {
      public static void Main(string[] args)
      {
          MyDelegate func1;
      }
    }
}

(2) C

//直接声明函数指针变量
char*(*func)(char* str1, char* str2);

//先定义函数指针类型,再声明变量
typedef char* (*MyDelegate)(char* str1, char* str2);
MyDelegate func1;

从对比中可以看出C#的委托定义语法和C还是很像的。

(3)Java

package com. example. coredx. practice;

@FunctionalInterface
public interface MyDelegate{
  String function(String str1, String str2);
}

Java中的 FunctionalInterface注解从 Java 8开始才有,之前的版本中只要接口中只定义一个方法即可,无法单纯从代码上分辨是函数式接口还是普通接口。

事件

事件是和委托配合使用的,为了确保使用事件的代码不会有意无意地调用或破坏委托,C#设计了事件。事件有点类似于属性,能确保只允许外部代码订阅或取消订阅事件,只允许类的内部成员调用触发。没有委托的Java自然无此问题。

C#的事件的示例代码如下:
 

using System;

namespace Example
{
  //定义事件参数
  public class MyEventArgs : EventArgs
   {
    private DateTime striggeredTime;
      private string smessage;

      public DateTime TriggeredTime
      {
        get {return striggeredTime;}
        }

       public string Message
        {
        get {return smessage};
       }
  
       public MyEventArgs(string message)
      {
        smessage= message;
          striggeredTime= DateTime. Now;
        }
    }

  //定义事件处理委托
  public delegate void MyEventHandler(object sender, MyEventArgs args);
  
  class Program
  {
    private static MyEventHandler smyEventHandler;

      //定义事件订阅访问器
    public static event MyEventHandler MyEvent
     {
        add { smyEventHandler += value; }
         remove { smyEventHandler -= value;}
       }

     //用简化语法定义事件订阅访问器
     public static event MyEventHandler MyEvent2;
  
     public static void Main(string[] args)
     {
      if (smyEventHandler != null) smyEventHandler. Invoke (null,new MyEventArgs("程序已启动。");
         if (MyEvent2 != null) MyEvent2.Invoke(null, new MyEventArgs("程序已启动。"));
       }
    }
}

从上面中可以看出C#的event关键字实际上是在类中定义事件处理委托的注册和取消访问器,本质上还是方法,事件本身还是靠委托实现的。也正是因为事件只允许进行注册和取消注册,因此可以避免外部代码有意或无意地调用事件处理委托误触发事件或直接通过赋值破坏事件处理委托。

上面代码中的“object sender, MyEventArgs args”是微软推荐的事件处理模式, WinForm控件的大多数事件都是按照这个模式设计的。其中sender表示引发事件的对象,args表示事件携带的数据。

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。

  • 20
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值