分享

C# delegate & event

 goodwangLib 2013-12-22

最近一直在学习C#的Delegate机制,总结在这里,以供大家参考。

 

参考资料:

http://www./services/dotnet_delegates_and_events.html

自己在CSDN的讨论:

http://bbs.csdn.net/topics/390334457

 

 

 一、基本理念

在事件处理模型中,Delegate充当产生事件的对象和处理事件的方法之间的媒介。

代理定义了控件的事件处理程序的签名,事件代理是广播式的,代理包含了方法引用列表。

把delegate理解为函数指针或者对函数的引用,而这种引用关系能够触发多个函数的调用。

在调用代理时,编译器不需要知道实际调用的是哪一个方法,从而实现动态绑定。

 

C# adds on value to the often mentioned world of event driven programming by adding support through events and delegates.

C#通过事件与代理实现事件驱动的编程方式。

 

An interesting and useful property of a delegate is that it does not know or care about the class of the object that it references. Any object will do; all that matters is that the method's argument types and return type match the delegate's. This makes delegates perfectly suited for "anonymous" invocation.

可以把delegate视为一种匿名函数调用。

 

代理的函数原型:

The signature of a single cast delegate is shown below:

delegate result-type identifier ([parameters]);

where:

  • result-type: The result type, which matches the return type of the function.
  • identifier: The delegate name.
  • parameters: The Parameters, that the function takes.

 

A delegate will allow us to specify what the function we'll be calling looks like without having to specifywhich function to call.

代理可以使我们只需指定要调用的函数的原型,而无需指定被调用的究竟是哪一个函数。

 

使用代理的3个步骤:声明--实例化--调用

There are three steps in defining and using delegates:

  • Declaration
  • Instantiation
  • Invocation

 

二、 delegate的一个简单应用(直接将delegate视为一个变量)

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestDelegate0
{
        class TestClass
        {
            //1.定义一个Delegate函数数据结构
            public delegate void GoDelegate();

            [STAThread]
            static void Main(string[] args)
            {
                //3.Delegate数据变量指向实例方法
                GoDelegate goDelegate = new GoDelegate(MyDelegateFunc);

                //4.通过Delegate数据变量执行实例方法
                goDelegate();
                return;
            }

            //2.定义Delegate将引用的静态方法或引用类实例及该类的实例方法
            public static void MyDelegateFunc()
            {
                Console.WriteLine("delegate function...");
            }
        }
   
}
复制代码

使用delegate要先进行声明,定义时给出函数签名。

Delegate声明相当于类的定义。

然后创建delegate的一个实例,并将其赋值。

在引用时直接引用该Delegate即可实现函数的绑定调用。

与函数指针的风格非常相似。

 

代理声明方法:

[public/private] delegate <返回值类型> <代理名称>(<参数列表>);

 

在代理的“实例化”的时候必须在构造函数中传入一个方法名。

 

 

三、静态函数的代理

A very basic example (SimpleDelegate1.cs):

using System;

namespace Akadia.BasicDelegate
{
    // Declaration
    public delegate void SimpleDelegate();

    class TestDelegate
    {
        public static void MyFunc()
        {
            Console.WriteLine("I was called by delegate ...");
        }

        public static void Main()
        {
            // Instantiation
            SimpleDelegate simpleDelegate = new SimpleDelegate(MyFunc);

            // Invocation
            simpleDelegate();
        }
    }
}

Compile an test:

# csc SimpleDelegate1.cs
# SimpleDelegate1.exe
I was called by delegate ...

 

由此可见,当前类的静态成员函数作为代理参数可以直接使用。

注意:如果直接代理类的非静态成员函数,将出现编译错误。

 

For our next, more advanced example (SimpleDelegate2.cs), declares a delegate that takes a single string parameter and has no return type:

把代理封装在类中,用类的成员通过代理实现函数调用:

注意:这里调用的是类的静态函数。

using System;

namespace Akadia.SimpleDelegate
{
    // Delegate Specification
    public class MyClass
    {
        // Declare a delegate that takes a single string parameter
        // and has no return type.
        public delegate void LogHandler(string message);

        // The use of the delegate is just like calling a function directly,
        // though we need to add a check to see if the delegate is null
        // (that is, not pointing to a function) before calling the function.
        public void Process(LogHandler logHandler)
        {
            if (logHandler != null)
            {
                logHandler("Process() begin");
            }

            if (logHandler != null)
            {
                logHandler ("Process() end");
            }
        }
    }

    // Test Application to use the defined Delegate
    public class TestApplication
    {
        // Static Function: To which is used in the Delegate. To call the Process()
        // function, we need to declare a logging function: Logger() that matches
        // the signature of the delegate.
        static void Logger(string s)
        {
            Console.WriteLine(s);
        }

        static void Main(string[] args)
        {
            MyClass myClass = new MyClass();

            // Crate an instance of the delegate, pointing to the logging function.
            // This delegate will then be passed to the Process() function.
            MyClass.LogHandler myLogger = new MyClass.LogHandler(Logger);
            myClass.Process(myLogger);
        }
    }
}

Compile an test:

# csc SimpleDelegate2.cs
# SimpleDelegate2.exe
Process() begin
Process() end

 

 四、成员函数的代理

using System;
using System.IO;

namespace Akadia.SimpleDelegate
{
    // Delegate Specification
    public class MyClass
    {
        // Declare a delegate that takes a single string parameter
        // and has no return type.
        public delegate void LogHandler(string message);

        // The use of the delegate is just like calling a function directly,
        // though we need to add a check to see if the delegate is null
        // (that is, not pointing to a function) before calling the function.
        public void Process(LogHandler logHandler)
        {
            if (logHandler != null)
            {
                logHandler("Process() begin");
            }

            if (logHandler != null)
            {
                logHandler ("Process() end");
            }
        }
    }

    // The FileLogger class merely encapsulates the file I/O
    public class FileLogger
    {
        FileStream fileStream;
        StreamWriter streamWriter;

        // Constructor
        public FileLogger(string filename)
        {
            fileStream = new FileStream(filename, FileMode.Create);
            streamWriter = new StreamWriter(fileStream);
        }

        // Member Function which is used in the Delegate
        public void Logger(string s)
        {
            streamWriter.WriteLine(s);
        }

        public void Close()
        {
            streamWriter.Close();
            fileStream.Close();
        }
    }

    // Main() is modified so that the delegate points to the Logger()
    // function on the fl instance of a FileLogger. When this delegate
    // is invoked from Process(), the member function is called and
    // the string is logged to the appropriate file.
    public class TestApplication
    {
        static void Main(string[] args)
        {
            FileLogger fl = new FileLogger("process.log");

            MyClass myClass = new MyClass();

            // Crate an instance of the delegate, pointing to the Logger()
            // function on the fl instance of a FileLogger.
            MyClass.LogHandler myLogger = new MyClass.LogHandler(fl.Logger);
            myClass.Process(myLogger);
            fl.Close();
        }
    }
}

The cool part here is that we didn't have to change the Process() function; the code to all the delegate is the same regardless of whether it refers to a static or member function.

Compile an test:

# csc SimpleDelegate3.cs
# SimpleDelegate3.exe
# cat process.log
Process() begin
Process() end

 

 由此可见,

 其它类的非静态函数作为代理参数需要先实例化类的对象然后传递这个对象的函数指针。

 

五、多路代理

多路委托

using System;
namespace MySample
{
    class TestClass
    {
        [STAThread]
        static void Main(string[] args)
        {
            DogDelegateClass dogDelegate = new DogDelegateClass();
            dogDelegate.ShowAnimalType("MySample");
        }
    }
    public class MyAnimalDelegateClass
    {
        public delegate void DelegateFunction(string strFuncName);
        private DelegateFunction m_delegateFunction = null;
        public DelegateFunction delegateFunction
        {
            get
            {
                return m_delegateFunction;
            }
            set
            {
                m_delegateFunction = value;
            }
        }
        public void ShowAnimalType(string strFuncName)
        {
            if (delegateFunction != null)
            {
                object[] args = {strFuncName};
                delegateFunction.DynamicInvoke(args);
            }
        }
    }
    public class DogDelegateClass:MyAnimalDelegateClass
    {
        public DogDelegateClass()
        {
      //多路委托函数 设定
            this.delegateFunction =  new DelegateFunction(subFunction31);
            this.delegateFunction += new DelegateFunction(subFunction32);
        }
  //委托函数1
        private void subFunction31(string strFuncName)
        {
            System.Console.WriteLine(
                string.Format("[{0}]This is a dog....", strFuncName));
        }
  //委托函数2
        private void subFunction32(string strFuncName)
        {
            System.Console.WriteLine(
                string.Format("[{0}]This is a nice dog....", strFuncName));
        }
    }
}

  多路委托可以将一个委托于多个函数绑定。

     

六、利用代理实现多态的实例(将delegate设为类的成员函数)

//利用代理实现多态的实例
using System;
namespace MySample
{
    class TestClass
    {
        [STAThread]
        static void Main(string[] args)
        {
            //狮子(LionDelegateClass)的属性显示(ShowAnimalType)方法调用
            LionDelegateClass lionDelegate = new LionDelegateClass();
            lionDelegate.ShowAnimalType("MySample");
            //马(HorseDelegateClass)的属性显示(ShowAnimalType)方法调用
            HorseDelegateClass horseDelegate = new HorseDelegateClass();
            horseDelegate.ShowAnimalType("MySample");
        }
    }
    //动物基类(MyAnimalDelegateClass)  (其中包含了delegate数据结构成员,用来实现动态绑定)
    public class MyAnimalDelegateClass
    {
        //Delegate数据结构定义
        public delegate void DelegateFunction(string strFuncName);
        //设代理为私有成员函数
        private DelegateFunction m_delegateFunction = null;
        //Delegate类型的属性
        public DelegateFunction delegateFunction
        {
            get
            {
                return m_delegateFunction;
            }
            set
            {
                m_delegateFunction = value;
            }
        }
        //属性显示(ShowAnimalType)方法
        public void ShowAnimalType(string strFuncName)
        {
            if (delegateFunction != null)
            {
                object[] args = { strFuncName };
                //调用Delegate引用的实例方法,实现动态绑定 (在实际调用的过程中,根据派生类绑定的不同delegate参数实现动态绑定)
                delegateFunction.DynamicInvoke(args);
            }
        }
    }
    //狮子(LionDelegateClass)
    public class LionDelegateClass : MyAnimalDelegateClass
    {
        //构造函数
        public LionDelegateClass()
        {
            this.delegateFunction = new DelegateFunction(subFunction1);
        }
        //狮子(LionDelegateClass)实例方法的实装
        private void subFunction1(string strFuncName)
        {
            System.Console.WriteLine(
               string.Format("[{0}]This is a lion....", strFuncName));
        }
    }
    //马(HorseDelegateClass)
    public class HorseDelegateClass : MyAnimalDelegateClass
    {
        //构造函数
        public HorseDelegateClass()
        {
            this.delegateFunction = new DelegateFunction(subFunction2);
        }
        //马(HorseDelegateClass)实例方法的实装
        private void subFunction2(string strFuncName)
        {
            System.Console.WriteLine(
               string.Format("[{0}]This is a horse....", strFuncName));
        }
    }
}

  不同的类可以绑定不同的方法。

 

 七、事件模型

The Event model in C# finds its roots in the event programming model that is popular in asynchronous programming. The basic foundation behind this programming model is the idea of "publisher and subscribers." In this model, you have publishers who will do some logic and publish an "event." Publishers will then send out their event only tosubscribers who have subscribed to receive the specific event.

In C#, any object can publish a set of events to which other applications can subscribe. When the publishing class raises an event, all the subscribed applications are notified. The following figure shows this mechanism.

事件模型起源于异步编程。

在C#中,任何对象都可以发布事件,其他的对象可以订阅这些事件。

当一个对象发布了一组事件以后,所有的订阅者都得到通知。

 

 

八、事件的习惯用法

The following important conventions are used with events:

  • Event Handlers in the .NET Framework return void and take two parameters.
  • 事件处理程序接受两个参数并返回空
  • The first paramter is the source of the event; that is the publishing object.
  • 事件处理程序的第一个参数是事件的发生者
  • The second parameter is an object derived from EventArgs.
  • 事件处理程序的第一个参数是从EventArgs派生的对象
  • Events are properties of the class publishing the event.
  • 要发布的这个事件,是类的属性。
  • The keyword event controls how the event property is accessed by the subscribing classes.
  • event关键字从语言的角度做了对事件的访问控制。

 

九、一个简单的事件示例

using System;
using System.IO;

namespace Akadia.SimpleEvent
{
    /* ========= Publisher of the Event ============== */
    public class MyClass
    {
        // Define a delegate named LogHandler, which will encapsulate
        // any method that takes a string as the parameter and returns no value
        public delegate void LogHandler(string message);
 
       
 // Define an Event based on the above Delegate
        public event LogHandler Log;
 
 
        // Instead of having the Process() function take a delegate
        // as a parameter, we've declared a Log event. Call the Event,
        // using the OnXXXX Method, where XXXX is the name of the Event.
        public void Process()
        {
            OnLog("Process() begin");
            OnLog
("Process() end");
        }
 
        // By Default, create an OnXXXX Method, to call the Event
        protected void OnLog(string message)
        {
            if (Log != null)
            {
                Log(message);
            }
        }
    }
 
    // The FileLogger class merely encapsulates the file I/O
    public class FileLogger
    {
        FileStream fileStream;
        StreamWriter streamWriter;
 
        // Constructor
        public FileLogger(string filename)
        {
            fileStream = new FileStream(filename, FileMode.Create);
            streamWriter = new StreamWriter(fileStream);
        }
 
        // Member Function which is used in the Delegate
        public void Logger(string s)
        {
            streamWriter.WriteLine(s);
        }
 
        public void Close()
        {
            streamWriter.Close();
            fileStream.Close();
        }
    }
 
    /* ========= Subscriber of the Event ============== */
    // It's now easier and cleaner to merely add instances
    // of the delegate to the event, instead of having to
    // manage things ourselves
    public class TestApplication
    {
        static void Logger(string s)
        {
            Console.WriteLine(s);
        }
 
        static void Main(string[] args)
        {
            FileLogger fl = new FileLogger("process.log");
            MyClass myClass = new MyClass();
 
            // Subscribe the Functions Logger and fl.Logger
            myClass.Log += new MyClass.LogHandler(Logger);
            myClass.Log += new MyClass.LogHandler(fl.Logger);

            // The Event will now be triggered in the Process() Method
            myClass.Process();

 
            fl.Close();
        }
    }
}

Compile an test:

# csc SimpleEvent.cs
# SimpleEvent.exe
Process() begin
Process() end
# cat process.log
Process() begin
Process() end

由此可见:

事件就是一个代理类型的变量,事件本身就是一个多路delegate。

C#中的事件就是代理的一个变量。它和属性、方法一样,都是类的成员。只不过事件是指向一个方法,当事件被触发时,就会执行对象的相关方法。

事件的这种对方法的引用并不是写死在代码里面的,而是可以进行更改的。辟如:我们在DotNet中按钮的OnClick事件,它可以指向符合OnClick事件签名的任何一个方法。

 

1.事件的定义使用event关键字:

  public event CryHandler DuckCryEvent;
  
   其中的CryHandler是一个delegate。从上面的代码我们可以看出来:事件就是一个代理类型的变量。

  private delegate void CryHandler();

2.指定事件处理程序:

   指定事件处理程序就是为事件挂接方法的过程。

  DuckCryEvent +=new CryHandler(Cry);

  public void Cry()
    {
        Console.WriteLine("我是一只小鸭,呀依呀依呀....");
    }
    
3.执行事件
  执行事件就是调用事件所指向方法的过程。一般对事的执行代码写在相应的方法或属性中,如果方法或属性被调用时就触发事件。

  public void BeShaked()
    {
        DuckCryEvent();
    }
    
4.完整的例子:
  //事件用到的代理,以般以×××Handler的格式进行命名
   private delegate void CryHandler();
   //玩具小鸭的类
    class Duck
    {
       //定义小鸭的唱歌事件
        public event CryHandler DuckCryEvent;
        public Duck()
        {
           //把小鸭唱歌的事件挂接到Cry方法上
            DuckCryEvent +=new CryHandler(Cry);
        }
        //小鸭唱歌事件对应的处理方法
        public void Cry()
        {
            Console.WriteLine("我是一只小鸭,呀呀呀....");
        }
        //小鸭被摇动
        public void BeShaked()
        {
           //执行事件
            DuckCryEvent();
        }
    }
    class Class2
    {
        public static void Main3(string[] args)
        {
           //买一只小鸭
            Duck d = new Duck();
            //摇一摇小鸭,它就会调触发小鸭的Cry事件,小鸭就会唱歌
            d.BeShaked();
        }
    }

 十、一个较复杂的事件示例

Suppose you want to create a Clock class that uses events to notify potential subscribers whenever the local time changes value by one second. Here is the complete, documented example:

using System;
using System.Threading;

namespace SecondChangeEvent
{
   /* ======================= Event Publisher =============================== */

   // Our subject -- it is this class that other classes
   // will observe. This class publishes one event:
   // SecondChange. The observers subscribe to that event.

   public class Clock
   {
      // Private Fields holding the hour, minute and second
      private int _hour;
      private int _minute;
      private int _second;

      // The delegate named SecondChangeHandler, which will encapsulate
      // any method that takes a clock object and a TimeInfoEventArgs
      // object as the parameter and returns no value. It's the
      // delegate the subscribers must implement.

      public delegate void SecondChangeHandler (
         object clock,
         TimeInfoEventArgs timeInformation
      );


      // The event we publish
      public event SecondChangeHandler SecondChange;

      // The method which fires the Event
      protected void OnSecondChange(
         object clock,
         TimeInfoEventArgs timeInformation
      )
      {
         // Check if there are any Subscribers
         if (SecondChange != null)
         {
            // Call the Event
            SecondChange(clock,timeInformation);
         }
      }


      // Set the clock running, it will raise an
      // event for each new second

      public void Run()
      {
         for(;;)
         {
            // Sleep 1 Second
            Thread.Sleep(1000);

            // Get the current time
            System.DateTime dt = System.DateTime.Now;

            // If the second has changed
            // notify the subscribers

            if (dt.Second != _second)
            {
               // Create the TimeInfoEventArgs object
               // to pass to the subscribers

               TimeInfoEventArgs timeInformation =
                  new TimeInfoEventArgs(
                  dt.Hour,dt.Minute,dt.Second);

               // If anyone has subscribed, notify them
               OnSecondChange (this,timeInformation);
            }

            // update the state
            _second = dt.Second;
            _minute = dt.Minute;
            _hour = dt.Hour;

         }
      }
   }

   // The class to hold the information about the event
   // in this case it will hold only information
   // available in the clock class, but could hold
   // additional state information

   public class TimeInfoEventArgs : EventArgs
   {
      public TimeInfoEventArgs(int hour, int minute, int second)
      {
         this.hour = hour;
         this.minute = minute;
         this.second = second;
      }
      public readonly int hour;
      public readonly int minute;
      public readonly int second;
   }

   /* ======================= Event Subscribers =============================== */

   // An observer. DisplayClock subscribes to the
   // clock's events. The job of DisplayClock is
   // to display the current time

   public class DisplayClock
   {
      // Given a clock, subscribe to
      // its SecondChangeHandler event

      public void Subscribe(Clock theClock)
      {
         theClock.SecondChange +=
            new Clock.SecondChangeHandler(TimeHasChanged);

      }

      // The method that implements the
      // delegated functionality

      public void TimeHasChanged(
         object theClock, TimeInfoEventArgs ti)
      {
         Console.WriteLine("Current Time: {0}:{1}:{2}",
            ti.hour.ToString(),
            ti.minute.ToString(),
            ti.second.ToString());
      }
   }

   // A second subscriber whose job is to write to a file
   public class LogClock
   {
      public void Subscribe(Clock theClock)
      {
         theClock.SecondChange +=
            new Clock.SecondChangeHandler(WriteLogEntry);

      }

      // This method should write to a file
      // we write to the console to see the effect

      // this object keeps no state
      public void WriteLogEntry(
         object theClock, TimeInfoEventArgs ti)
      {
         Console.WriteLine("Logging to file: {0}:{1}:{2}",
            ti.hour.ToString(),
            ti.minute.ToString(),
            ti.second.ToString());
      }
   }

   /* ======================= Test Application =============================== */

   // Test Application which implements the
   // Clock Notifier - Subscriber Sample

   public class Test
   {
      public static void Main()
      {
         // Create a new clock
         Clock theClock = new Clock();

         // Create the display and tell it to
         // subscribe to the clock just created

         DisplayClock dc = new DisplayClock();
         dc.Subscribe(theClock);

         // Create a Log object and tell it
         // to subscribe to the clock

         LogClock lc = new LogClock();
         lc.Subscribe(theClock);

         // Get the clock started
         theClock.Run();
      }
   }

}

 

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多