zoukankan      html  css  js  c++  java
  • c#委托总结

    1.委托可以把方法当作参数在另一个方法中传递和调用

           本例中委托的定义:public delegate void GreetingDelegate(string name);

          控制台内主方法,调用GreatPeople内的方法,一个是String类型,另一个就是将方法作为参数传递;  

          static void Main(string[] args)
          {
              GreetPeople("张华", ChineseGreet);
             GreetPeople("Jenny", EnglishGreet);
             Console.ReadLine();
          }

          public void GreetPeople(string name, GreetingDelegate MakeGreeting)

          {

               //将委托实例化,接收参数内name 的值,并和参数一样传递需要调用的方法

               MakeGreeting(name);

          }

         两个作为参数传递的方法

        public static void ChineseGreet(string name)
        {
               Console.WriteLine("早上好," + name);
        }

        private static void ChineseGreeting(string name)

        {

                Console.WriteLine("早上好, " + name);

        }

    2.利用委托实现窗体之间传值

       本例委托是窗体B的文本框内的值传值给窗体A。

       本例在窗体B内定义委托:

           public event Action<string> SetTextEvent;

       然后在窗体A内定义一个方法:

        

       private void SetTxt(string txta)
      {
            txtA.Text = txta;
       }

       接着在窗体A为窗体B绑定事件处理函数

      

       private void button1_Click(object sender, EventArgs e)
       {
            Frm_B fb = new Frm_B();
           //为窗体B绑定事件处理函数
            fb.SetTextEvent += SetTxt;
            fb.ShowDialog();
        }

        窗体B内的按钮的事件

         private void btnB_Click(object sender, EventArgs e)
        {
             SetTextEvent(txtB.Text);
        }

        这样子就是实现了从窗体B内的文本框传值给窗体A了。

    3.委托、事件与Observer设计模式

      假设热水器由三部分组成:热水器、警报器、显示器,它们来自于不同厂商并进行了组装。那么,应该是热水器仅仅负责烧水,它不能发出警报也不能显示水温;在水烧开时由警报器发出警报、显示器显示提示和水温。

      这时候,例子就应该变成这个样子:

      

    public class Heater
    {
         private int temparature;
         public delegate void BoilHandler(int parm); //声明委托
         public event BoilHandler BoilEvent; //声明对象

         public void BoilWater()
        {
           for(int i = 95; i <= 100; i++)
           {
                temparature = i;

                if(temparature > 95)
               {
                      BoilEvent?.Invoke(temparature);
                      /*上面是lambda表达式写法
                      if (BoilEvent != null)
                     {
                            BoilEvent(temparature);
                     }
                      */
                }
             }
           }
    }
    //警报器
    public class Alarm
    {
        public void MakeAlert(int param)
       {
            Console.WriteLine("Alart:嘀嘀嘀,水已经{0}度了", param);
        }
    }
    //显示器
    public class Display
    {
        public static void ShowMsg(int param) //静态方法
        {
             Console.WriteLine("Dispaly:水快开了,当前已经{0}度", param);
         }
    }

           这里就出现了一个问题:如何在水烧开的时候通知报警器和显示器?在继续进行之前,我们先了解一下Observer设计模式,Observer设计模式中主要包括如下两类对象:

    1. Subject:监视对象,它往往包含着其他对象所感兴趣的内容。在本范例中,热水器就是一个监视对象,它包含的其他对象所感兴趣的内容,就是temprature字段,当这个字段的值快到100时,会不断把数据发给监视它的对象。
    2. Observer:监视者,它监视Subject,当Subject中的某件事发生的时候,会告知Observer,而Observer则会采取相应的行动。在本范例中,Observer有警报器和显示器,它们采取的行动分别是发出警报和显示水温。

          在本例中,事情发生的顺序应该是这样的:

    1. 警报器和显示器告诉热水器,它对它的温度比较感兴趣(注册)。
    2. 热水器知道后保留对警报器和显示器的引用。
    3. 热水器进行烧水这一动作,当水温超过95度时,通过对警报器和显示器的引用,自动调用警报器的MakeAlert()方法、显示器的ShowMsg()方法。

           类似这样的例子是很多的,GOF对它进行了抽象,称为Observer设计模式:Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。Observer模式是一种松耦合的设计模式。

         static void Main(string[] args)
         {
            Heater heater = new Heater();
            Alarm alarm = new Alarm();

           heater.BoilEvent += alarm.MakeAlert;
           heater.BoilEvent += Display.ShowMsg;

            heater.BoilWater();
            Console.ReadLine();
         }

    4.委托定义匿名内部类

       1.讲匿名内部类前,先讲一下Invoke

           Invoke的中文解释是唤醒,它有两种参数类型我们这里只讲一种即(Delegate, Object[])

           Delegate就是前面提到的那个代理,而Object[]则是用来存放Delegate所代理函数的参数

           MSDN上关于INVOKE方法有如下说明:在拥有控件的基础窗口句柄的线程上,用指定的参数列表执行指定委托。

           用通俗的话讲就是利用控件的INVOKE方法,使该控件所在的线程执行这个代理,也就是执行我们想对控件进行的操作,相当于唤醒了这个操作;

       2.接着讲解一下如何利用委托实现线程改变控件的外观,确保不发生线程冲突。

       

        在用.NET Framework框架的WinForm构建GUI程序界面时,如果要在控件的事件响应函数中改变控件的状态,例如:某个按钮上的文本原先叫“打开”,单击之后按钮上的文本显       示“关闭”,初学者往往会想当然地这么写:

       void ButtonOnClick(object sender,EventArgs e)

       {

             button.Text="关闭";

        }

            这样的写法运行程序之后,可能会触发异常,异常信息大致是“不能从不是创建该控件的线程调用它”。注意这里是“可能”,并不一定会触发该种异常。造成这种异常的原因在      于,控件是在主线程中创建的(比如this.Controls.Add(...);),进入控件的事件响应函数时,是在控件所在的线程,并不是主线程。在控件的事件响应函数中改变控件的状态,可能与  主线程发生线程冲突。如果主线程正在重绘控件外观(Main.refresh()),此时在别的线程改变控件外观,就会造成画面混乱。不过这样的情况并不总会发生,如果主线程此时在重绘别的控件,就可能逃过一劫,这样的写法可以正常通过,没有触发异常。

          正确的写法是在控件响应函数中调用控件的Invoke方法(其实如果大家以前用过C++ Builder的话,也会找到类似Invoke那样的激活到主线程的函数)。Invoke方法会顺着控件树向上搜索,直到找到创建控件的那个线程(通常是主线程),然后进入那个线程改变控件的外观,确保不发生线程冲突。正确写法的示例如下:

    void ButtonOnClick(object sender,EventArgs e)

    {

        button.Invoke(new EventHandler(delegate

        {

            button.Text="关闭";

        }));

    }

       这样的写法有一个烦人的地方:对不同的控件写法不同。对于TextBox,要TextBoxObject.Invoke,对于Label,又要LabelObject.Invoke。有没有统一一点的写法呢?

       主窗口类本身也有Invoke方法。如果你不想对不同的控件写法不一样,可以全部用this.Invoke

       void ButtonOnClick(object sender,EventArgs e)

      {

          this.Invoke(new EventHandler(delegate

          {

              button.Text="关闭";

          }));

      }

    5.使用lamda表达式简化委托

    C# 3.0及以后的版本中有了Lamda表达式,NET Framework 3.5及以后版本更能用Action封装方法。例如以下写法可以看上去非常简洁:

    void ButtonOnClick(object sender,EventArgs e)

    {

        this.Invoke(new Action(()=>

        {

            button.Text="关闭";

        }));

    }

    6.委托是方法的快捷方式

       思想:把想对另一线程中的控件实施的操作放到一个函数中,然后使用delegate代理那个函数,并且在那个函数中加入一个判断,用 InvokeRequired来判断调用这个函数的线程是否和控件线程在同一线程中,如果是则直接执行对控件的操作,否则利用控件的Invoke或 BeginInvoke方法来执行这个代理。

       Invoke方法需要创建一个委托。你可以事先写好函数和与之对应的委托。不过,若想直观地在Invoke方法调用的时候就看到具体的函数,而不是到别处搜寻的话,上面的示例代码是不错的选择。

       MSDN中说:
       获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。  
       如果控件的 Handle 是在与调用线程不同的线程上创建的(说明您必须通过 Invoke 方法对控件进行调用),则为 true;否则为 false。
       Windows 窗体中的控件被绑定到特定的线程,不具备线程安全性 。因此,如果从另一个线程调用控件的方法,那么必须使用控件的一个 Invoke 方法来将调用封送到适当的线程。该属性可用于确定是否必须调用 Invoke 方法,当不知道什么线程拥有控件时这很有用。

       下面来说下这个的用法(我的一般做法):
        首先定义一个委托,与这个事件处理函数的签名一样委托,当然直接使用该事件的委托也是可以的,如:  

        private delegate void InvokeCallback( string msg);

        然后就是判断这个属性的值来决定是否要调用Invoke函数:

       void m_comm_MessageEvent( string msg)
       {
             if (txtMessage.InvokeRequired)
             {
                 InvokeCallback  msgCallback = new InvokeCallback(m_comm_MessageEvent);
                 txtMessage.Invoke(msgCallback, new object [] { msg } );

              } 
             else 
            {
                txtMessage.Text = msg;
             } 

        }

         说明:这个函数就是事件处理函数,txtMessage是一个文本框。
         这样就做到了窗体中控件的线程安全性。

  • 相关阅读:
    NYOJ 10 skiing DFS+DP
    51nod 1270 数组的最大代价
    HDU 4635 Strongly connected
    HDU 4612 Warm up
    POJ 3177 Redundant Paths
    HDU 1629 迷宫城堡
    uva 796
    uva 315
    POJ 3180 The Cow Prom
    POJ 1236 Network of Schools
  • 原文地址:https://www.cnblogs.com/Qxian/p/8667260.html
Copyright © 2011-2022 走看看