• 委托:一种引用类型,这种类型可以用来定义方法签名,从而使用委托实现将方法作为参数传递给其他方法。类似于C++中的函数之争,使用委托使程序员可以将方法引用封装在委托对象内
    1. 定义和声明委托:

       delegate 返回值 委托名(参数列表);
      eg:
      public delegate void SayHelloDelegate(string name);
    2. 使用委托:委托其实通过返回值和参数列表来定义方法和签名。任何与委托具有相同返回值和参数列表(签名)的方法都可以赋给该委托。
       public delegate void SayHelloDelegate(string name);//声明委托
      public viod SayHelloEnglish(string name)//声明方法
      {
      Console.WriteLine("Hello,{0}",name);
      }
      SayHelloDelegate s=SayHelloEnglish;//把方法赋予委托对象

      委托实例化的几种形式:

       .使用new关键字
      SayHelloDelegate s=new SayHelloDelegate(SayHelloEnglish);
      .直接赋值
      SayHelloDelegate s=SayHelloEnglish;
      .使用匿名方法
      SayHelloDelegate s=delegate(string name)
      {
      Console.WriteLine("Hello,{0}",name);
      }
      .使用Lambda表达式,例如:
      SayHelloDelegate s=name=>
      {
      Console.WriteLine("Hello,{0}",name);
      }
       委托实例化后,就可以像调用方法一样调用委托。eg:
      s("John");
    3. 多播委托:一个委托对象可以包含多个方法。调用多播委托时,按赋值的顺序一次执行每个方法。
      1. 实现多播委托
        1. 使用运算符+实现:委托对象的一个有用属性,可以使用+运算符将多个对象分配给一个委托实例。这里运算符+操作对象只能是委托对象,不能使方法签名。

           public delegate void SayHelloDelegate(string name);
          public void SayHelloEnglish(string name)
          {
          Console.WriteLine("Hello,{0}",name);
          }
          public void SayHelloChinese(string name);
          {
          Console.WriteLine("你好,{0}",name);
          }
          SayHelloDelegate s1=new SayHelloDelegate(SayHelloEnglish);
          SayHelloDelegate s2=SayHelloChinese;
          SayHelloDelegate s3=s1+s2;//操作符两边只能是委托对象
        2. 使用运算符+=
           public delegate void SayHelloDelegate(string name);
          public void SayHelloEnglish(string name)
          {
          Console.WriteLine("Hello,{0}",name);
          }
          public void SayHelloChinese(string name);
          {
          Console.WriteLine("你好,{0}",name);
          }
          SayHelloDelegate s=new SayHelloDelegate(SayHelloEnglish);
          SayHelloDelegate s+=SayHelloChinese;//右边的操作对象是方法签名
      2. 特点:
        1. 多播委托可以包含多个方法
        2. 多播委托包含的方法必须返回viod,否则会抛出run-time exception
        3. 只能合并相同类型的委托
      3. 从多播委托中移除组件
        1. 使用运算符-,两侧必须为委托对象
        2. 使用运算符-=,右侧可以为方法名
    4. 匿名方法:相对于命名方法(以前接触的方法都是命名方法,都是包含方法名的)来说,顾名思义,就是没有命名的方法,就是作为一个整体的一些代码块。想要实现匿名方法,使用委托是唯一途径,把整个代码块作为参数赋予委托对象,然后调用委托对象,从而实现方法的执行。
       //创建委托
      delegate void Del(string name);
      //实现匿名方法
      Del d=delegate(string name)
      { //code block
      };

summary:

      1. 匿名方法的定义是以关键字delegate开始,后面跟着参数列表和方法主体。
      2. 使用匿名方法,可以省去一个编写方法的过程,从而使代码看起来更简洁。
      3. 使用匿名方法可以减少实例化委托的代码系统开销。

attention:

      1. 在匿名方法中不能使用跳转语句跳转到该匿名方法的外部,匿名方法外部的跳转语句不能跳转到匿名方法的内部。
      2. 匿名方法内部不能访问不安全代码。
      3. 不能再匿名方法外部使用ref和out参数,但可以使用在匿名方法外部定义的其他变量。
         public delegate void MyDelegate();
        class Program
        {
        static void Main(string[] args)
        {
        Program p=new Program();
        for(int i=;i<=;i++)
        p.TestInstanceDataMembers();
        Console.ReadLine();
        }
        //测试方法
        public void TestInstanceDataMembers()
        {
        //声明匿名方法并赋值给对象
        MyDelegate d=delegate
        {
        //操作外部局部变量
        Console.WriteLine("Count:{0}",++m_iCount);
        };
        d();//执行方法
        }
        public int m_iCount=;//定义外部变量
        }
         Count:
        Count:
        Count:
        Count:
        Count:
    1. 委托中的协变和逆变:将方法签名与委托类型匹配时,协变和逆变提供了一定程度的灵活性。协变允许方法具有的派生返回类型比委托中定义的更多。逆变允许方法具有的派生参数类型比委托类型中的更少
      1. 协变方法签名与委托类型匹配的灵活性体现在方法返回类型上。eg:

         Shape
        {
        //类主体
        }
        Point:Shape
        {
        //类主体
        }
        delegate Shape DoDelegate();
        static Shape FirstDo()
        {
        return null;
        }
        static Point SecondDo()
        {
        return null;
        }
        DoDelegate Do1=FirstDo;
        //协变允许实现这样的委托
        DoDelegate Do2=SecondDo;

        协变允许返回类型更多体现在类的派生上

      2. 逆变使方法签名与委托类型匹配的灵活性体现在参数上。eg:
         System.DateTime lastActivity;
        public From1()
        {
        InitializeComponent();
        lastActivity=new System.DateTime();
        this.textBox1.KeyDown+=this.MultiHandler;//触发KeyEventArgs
        this.botton1.MouseClick+=this.MultiHandler;//触发MouseEventArgs
        }
        private void MultiHandler(object sender,System.EvenArgs e)
        {
        lastActivity=System.DateTime.Now;
        }

        以上代码中只创建一个接收EventArgs输入参数的事件处理程序,然后,可以将该处理程序与发送MouseEventArgs类型作为参数的Botton.MouseClick事件一起使用,也可以将该处理程序与发送KeyEventArgs参数的TextBox.KeyDown事件一起使用。

    2. 回调函数:委托实例化后,就可以将其作为参数进行传递,方法遍可以将一个委托作为参数来接受,并且以后可以调用该委托,这称为回调函数
       delegate void Del(string name);//定义委托
      void Do(string)//定义方法
      {
      }
      void FirstDo(string s,Del del)//把委托作为参数传递进来
      {
      del(s);//调用委托
      }
      Del del=new Del(Do);//初始化委托
      FirstDo(“你好",del);//调用方法
      1. 属于工作流的一个部分
      2. 必须按照工作流指定的调用约定来声明(定义)
      3. 它的调用时机由工作流决定,回调函数的实现者不能直接调用回调函数来实现工作流的功能。
      4. 使用回调机制,可以为工作流实现扩展。可以把工作流中需要用户干预的,或者需要提供给用户的数据以回调的模式提供给用户。而用户不需要知道整个工作的流程,只需要知道回调函数的说明就可以使用工作流模块提供的功能,这对信息的隐藏也是有作用的。
         class SumClass
        {
        public delegate int Sum(int num1,int num2);委托
        public int SumAll(int num1,int num2,Sum sun)//把委托实例作为参数传递
        {
        return sun(num1,num2);//回调函数
        }
        }
        class Program
        {
        static void Main(string[] args)
        {
        Program prog=new Program();
        SumClass sumClass=new SumClass();
        int result=sunClass.SumAll(,,prog.GetSum);//回调函数GetSum
        Console.WriteLine(result.ToString());
        Console.ReadLine();
        }
        private int GetSum(int a,int b)
        {
        return a+b;
        }
        }
  • 事件:所有与用户交互的过程基本上都是事件触发和处理的过程。
    1. 定义和声明事件:事件其实就是消息,事件的本质就是对消息的封装,用作对象之间的通信:事件的发起方称为事件发送器,事件的接收方称为事件的接收器。在C#中提供了一个名为EventHandler的预定义委托,专用于表示不生成数据的事件的事件处理方法

      这个委托的预定义如下:
      public delegate void EventHandler(
      object sender,//引用引发事件的实例
      EventArgs e//从EventArgs类型派生,保存事件数据
      )

      EventHandler专用于表示不生成数据的事件的事件处理程序方法。如果事件生成数据,则必须提供自定义数据类型,并创建一个委托,其中第二个参数为自定义参数类型。若要将事件与处理事件的方法关联,还要向事件添加委托的实例,这时需要使用关键字event来声明。

       //访问修饰符event EventHandler 事件名
      
       public event EventHandler NoDataEventHandler;
      //定义了没有数据的事件成员
    2. 定义事件处理程序:定义了事件成员,就可以把事件处理程序关联到事件上。事件处理程序委托会被绑定到系统引发事件时要执行的方法。事件处理程序会被添加到事件中,以便当事件引发时,事件处理程序能够调用它的方法,这就是订阅事件。订阅事件的处理过程如下:
      1. 编写事件处理程序

         void HandleCustomEvent(object sender,CustomEventArgs a)
        {
        //处理程序
        }
      2. 使用加法赋值运算符+=来为事件附加处理程序,eg:
         publisher.RaiseCustomEvent+=HandlerCustomEvent;

        当然如果用匿名方法或Lambda表达式,可以不用事先编写事件处理程序,直接把匿名方法或Lambda表达式绑定到事件上。事件是一种特殊类型的委托,支持多播委托,因此可以把多个事件处理程序绑定到一个事件中。

      3. 创建并使用事件
         using cs002;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        using System.Threading.Tasks; namespace cs002
        {
        public class A
        {
        public delegate void EventHandler(object sender, EventArgs e);//定义委托
        public event EventHandler a;//声明事件
        public void Run(EventArgs e)//引发事件
        {
        Console.WriteLine("产生一个事件。");
        a(this, e);//处理当前引发的事件
        }
        }
        class B
        {
        public B(A a)
        {
        a.a += new A.EventHandler(this.b); //订阅事件处理程序
        }
        private void b(object sender,EventArgs e)//事件处理的方法
        {
        Console.WriteLine("处理事件!");
        Console.Read();
        }
        }
        class prg
        {
        public static void Main(string[] args)
        {
        A a = new A();
        B b = new B(a);
        EventArgs e = new EventArgs();
        a.Run(e);
        }
        }
        }
      4. 从EventArgs类派生:EventArgs类是包含事件数据的类的基类。此类不包含事件数据,在事件的引发时不向事件处理程序传递状态信息的事件会使用此类。EventArgs类本身并没有什么可介绍的,它只提供了一个只读字段Empty,表示事件没有数据。如果事件处理需要状态信息,则应用程序必须从EventArgs类派生一个类来保存数据。例如创建一个KeyEventArgs类,该类保存键盘按键事件中要包含的按键信息。
         internal class KeyEventArgs:EventArgs
        {
        private char keyChar;//按键信息
        public KeyEventArgs(char keyChar):base()
        {
        this.keyChar=keyChar;
        }
        public char KeyChar//按键信息
        {
        get
        {
        return keyChar;
        }
        }
        }

        创建并使用派生事件

         using cs002;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        using System.Threading.Tasks; namespace cs002
        {
        //触发火警事件
        public class FireEventArgs:EventArgs
        {
        public FireEventArgs (string room,int ferocity)
        {
        this.room = room;
        this.ferocity = ferocity;
        }
        public string room;
        public int ferocity;//火势等级
        }
        public class FireAlarm
        {
        public delegate void FireEventHandler(object sender, FireEventArgs fe);
        //创建火警事件委托
        public event FireEventHandler FireEvent;
        //触发火警事件 和委托相联系/类型相同
        public void ActivateFireAlarm(string room,int ferocity)
        {
        FireEventArgs fireArgs = new FireEventArgs(room, ferocity);
        //调用委托
        FireEvent(this, fireArgs);
        }
        }
        //火警事件处理类
        class FireHandlerClass
        {
        public FireHandlerClass (FireAlarm fireAlarm)
        {
        //订阅火警事件处理程序
        fireAlarm.FireEvent += new FireAlarm.FireEventHandler(ExtinguishFire);//多播委托
        }
        //火警事件处理程序
        void ExtinguishFire(object sender,FireEventArgs fe)
        {
        Console.WriteLine("\n火警事件是由{0}引发的:", sender.ToString());
        if (fe.ferocity < )
        Console.WriteLine("发生在{0}的火警是没有问题的,用水就可以浇灭。", fe.room);
        else if (fe.ferocity < )
        Console.WriteLine("要使用灭火器才能扑灭{0}的大火。", fe.room);
        else
        Console.WriteLine("发生在{0}的大火已经失控,请通知政府部门!", fe.room);
        }
        }
        public class program
        {
        public static void Main(string[] args)
        {
        //创建火警对象
        FireAlarm myFireAlarm = new FireAlarm();
        //创建火警事件处理程序对象
        FireHandlerClass myFireHandler = new FireHandlerClass(myFireAlarm);
        //触发火警事件
        myFireAlarm.ActivateFireAlarm("厨房", );
        myFireAlarm.ActivateFireAlarm("书房", );
        myFireAlarm.ActivateFireAlarm("车库", );
        }
        }
        }
      5. 在派生类中引发基类事件:事件作为类的成员,一般都被定义为public类型,但是派生类并不能直接调用基类中声明的事件。但可以在包含该事件的基类中创建一个受保护的调用方法。通过调用或重写此调用方法,派生类便可简介调用该事件。
         using cs002;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        using System.Threading.Tasks; namespace cs002
        {
        public class ShapeEventArgs:EventArgs
        {
        private double newArea;
        public ShapeEventArgs(double d)
        {
        newArea = d;
        }
        public double NewArea
        {
        get { return newArea; }
        }
        }
        //定义图形类Shape,在该类中定义事件
        public abstract class Shape
        {
        public delegate void shapeEventHandler(object sender, ShapeEventArgs e);
        public double area;
        public event shapeEventHandler ShapeChanged;
        public abstract void Draw();
        //受保护的触发事件的方法
        protected virtual void OnShapeChanged(ShapeEventArgs e)
        {
        shapeEventHandler handler = ShapeChanged;
        if(handler!=null)
        {
        handler(this, e);
        }
        }
        }
        public class Circle:Shape
        {
        private double radius;
        public Circle(double d)
        {
        radius = d;
        area = 3.14 * d * d;
        }
        public void Update(double d)
        {
        radius = d;
        area = 3.14 * d * d;
        OnShapeChanged(new ShapeEventArgs(area));//触发事件
        }
        //重写受保护的触发事件的方法
        protected override void OnShapeChanged(ShapeEventArgs e)
        {
        //调用基类的方法以触发事件
        base.OnShapeChanged(e);
        }
        public override void Draw()
        {
        Console.WriteLine("画一个圆");
        }
        }
        //事件处理程序
        public class ShapeHandler
        {
        public ShapeHandler (Shape s)
        {
        //订阅事件
        s.ShapeChanged += new Shape.shapeEventHandler(HandleShapeChanged);
        }
        //事件处理程序
        private void HandleShapeChanged(object sender,ShapeEventArgs e)
        {
        Shape s = (Shape)sender;
        //显示图形信息
        Console.WriteLine("图形更改事件是由{0}引起的,图形的面积更新后是:{1}", sender.ToString(), e.NewArea);
        //绘制图形
        s.Draw();
        }
        }
        public class program
        {
        public static void Main(string[] args)
        {
        Circle c1 = new Circle();
        Console.WriteLine("更新前的图形面积是:{0}", c1.area);
        ShapeHandler myShapeHandler = new ShapeHandler(c1);
        c1.Update();
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
        }
        }
        }
      6. 实现接口事件
         public interface Iobject//定义接口
        {
        event EventHandler OnChangedEventArgs;
        }
        public class MyEventArgs:EventArgs
        {
        }
        public class A:Iobject
        {
        public event EventHandler OnChangedEventArgs;
        void ChangedA{
        OnChanged(new MyEventArgs(/*arguments*/));
        }
        protected virtual void OnChanged(MyEventArgs e)
        {
        if(OnChangedEventArgs!=null)
        {
        OnChangedeEventArgs(this,e);
        }
        }
        }

c#学习笔记03——委托和事件的更多相关文章

  1. [读书笔记]C#学习笔记二: 委托和事件的用法及不同.

    前言:  C#委托是什么 c#中的委托可以理解为函数的一个包装, 它使得C#中的函数可以作为参数来被传递, 这在作用上相当于C++中的函数指针. C++用函数指针获取函数的入口地址, 然后通过这个指针 ...

  2. 机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理、源码解析及测试

    机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理.源码解析及测试 关键字:决策树.python.源码解析.测试作者:米仓山下时间:2018-10-2 ...

  3. OpenCV 学习笔记03 边界框、最小矩形区域和最小闭圆的轮廓

    本节代码使用的opencv-python 4.0.1,numpy 1.15.4 + mkl 使用图片为 Mjolnir_Round_Car_Magnet_300x300.jpg 代码如下: impor ...

  4. OpenCV 学习笔记03 findContours函数

    opencv-python   4.0.1 1 函数释义 词义:发现轮廓! 从二进制图像中查找轮廓(Finds contours in a binary image):轮廓是形状分析和物体检测和识别的 ...

  5. C++ GUI Qt4学习笔记03

    C++ GUI Qt4学习笔记03   qtc++spreadsheet文档工具resources 本章介绍创建Spreadsheet应用程序的主窗口 1.子类化QMainWindow 通过子类化QM ...

  6. SaToken学习笔记-03

    SaToken学习笔记-03 如果排版有问题,请点击:传送门 核心思想 所谓权限验证,验证的核心就是一个账号是否拥有一个权限码 有,就让你通过.没有?那么禁止访问! 再往底了说,就是每个账号都会拥有一 ...

  7. Redis:学习笔记-03

    Redis:学习笔记-03 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 7. Redis配置文件 启动 ...

  8. C#事件和委托(C#学习笔记03)

    委托 1. C# 中的委托类似于 C 或 C++ 中指向函数的指针.委托表示引用某个方法的引用类型变量,运行时可以更改引用对象. 2. 特别地,委托可以用于处理事件或回调函数.并且,所有的委托类都是从 ...

  9. 《C#高级编程》学习笔记------C#中的事件和委托

    本文转载自张子阳 目录 委托的作用 将方法绑定到委托 事件的来由 Observer设计模式 .Net Framework中的委托与事件   引言 委托 和 事件在 .Net Framework中的应用 ...

随机推荐

  1. Docker部署Python应用程序

    Docker部署Python应用程序 1. 单个py文件部署 生成Dockerfile 文件 插件用的豆瓣的镜像,,重置时间(容器的默认时间是UTC时间与宿主机的相差8小时). 文中需要三个插件(pe ...

  2. ES6中新增let命令使用方法

     在ES6中新增了let命令,该命令的用法与var 类似,但是所声明的变量只能在let命令所在的代码块(最接近let 命令的大括号内)中有效果.但是let 又有一些不同于var 的特性. 1.let定 ...

  3. UVA - 10384 The Wall Pusher(推门游戏)(IDA*)

    题意:从起点出发,可向东南西北4个方向走,如果前面没有墙则可走:如果前面只有一堵墙,则可将墙向前推一格,其余情况不可推动,且不能推动游戏区域边界上的墙.问走出迷宫的最少步数,输出任意一个移动序列. 分 ...

  4. 线程与进程 queue模块

    queue模块的基本用法 https://www.cnblogs.com/chengd/articles/7778506.html 模块实现了3种类型的队列,区别在于队列中条目检索的顺序不同.在FIF ...

  5. 剑指offer_2.1_Day_5

    输入一个链表,按链表从尾到头的顺序返回一个ArrayList. import java.util.ArrayList; public class Solution { public ArrayList ...

  6. 201909-2 小明种苹果(续) Java

    思路: 待补充 import java.util.*; public class Main { public static void main(String[] args) { Scanner sc ...

  7. 关于torch.flatten的笔记

    先看函数参数: torch.flatten(input, start_dim=0, end_dim=-1) input: 一个 tensor,即要被“推平”的 tensor. start_dim: “ ...

  8. 2020PHP面试-PHP篇

    一.列举一些PHP的设计模式 单例模式:保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个,同时这个类还必须提供一个访问该类的全局访问点. 工厂模式:定义一个创建对象的接口,但是让 ...

  9. 18 11 27 高级的服务器连接 epoll

    ---恢复内容开始--- 之前的  http 服务器  都是采用 轮询的方式(就像 厨师挨个问谁饿了好做饭 一样  ) 而  epoll 用着高级的 方式  事件通知 (直接问谁饿了) 同时还和  计 ...

  10. 操作实践:maven工程查找工程中多余的jar包

    声明:迁移自本人CSDN博客https://blog.csdn.net/u013365635 版本迭代过程中对jar的依赖可能会产生变化,一些本不必再依赖的jar包可以因为没有清除而依然留在版本的发布 ...