.NET基础拾遗(4)委托和事件2

事件

事件是对象发送的消息,以发信号通知操作的发生。操作可能是由用户交互(例如鼠标单击)引起的,也可能是由某些其他的程序逻辑触发的。

引发事件的对象称为事件发送方。捕获事件并对其作出响应的对象叫做事件接收方。

 在事件通信中,事件发送方类不知道哪个对象或方法将接收到(处理)它引发的事件。所需要的是在源和接收方之间存在一个媒介(或类似指针的机制)。

.NET Framework 定义了一个特殊的类型(Delegate),该类型提供函数指针的功能。

 

Microsoft的产品文档定义的事件:事件是一种使对象或类能够提供通知的成员

public class ConsoleEventArgs : EventArgs
    {
        // 控制台输出的消息
        private string message;

        public string Message
        {
            get
            {
                return message;
            }
        }

        public ConsoleEventArgs()
            : base()
        {
            this.message = string.Empty;
        }

        public ConsoleEventArgs(string message)
            : base()
        {
            this.message = message;
        }
    }


/// <summary>
    /// 管理控制台,在输出前发送输出事件
    /// </summary>
    public class ConsoleManager
    {
        // 定义控制台事件成员对象
        public event EventHandler<ConsoleEventArgs> ConsoleEvent;

        /// <summary>
        /// 控制台输出
        /// </summary>
        public void ConsoleOutput(string message)
        {
            // 发送事件
            ConsoleEventArgs args = new ConsoleEventArgs(message);
            SendConsoleEvent(args);
            // 输出消息
            Console.WriteLine(message);
        }

        /// <summary>
        /// 负责发送事件
        /// </summary>
        /// <param name="args">事件的参数</param>
        protected virtual void SendConsoleEvent(ConsoleEventArgs args)
        {
            // 定义一个临时的引用变量,确保多线程访问时不会发生问题
            EventHandler<ConsoleEventArgs> temp = ConsoleEvent;
            if (temp != null)
            {
                temp(this, args);
            }
        }
    }

/// <summary>
    /// 日志类型,负责订阅控制台输出事件
    /// </summary>
    public class Log
    {
        // 日志文件
        private const string logFile = @"C:\TestLog.txt";

        public Log(ConsoleManager cm)
        {
            // 订阅控制台输出事件
            cm.ConsoleEvent += this.WriteLog;
        }

        /// <summary>
        /// 事件处理方法,注意参数固定模式
        /// </summary>
        /// <param name="sender">事件的发送者</param>
        /// <param name="args">事件的参数</param>
        private void WriteLog(object sender, EventArgs args)
        {
            // 文件不存在的话则创建新文件
            if (!File.Exists(logFile))
            {
                using (FileStream fs = File.Create(logFile)) { }
            }

            FileInfo fi = new FileInfo(logFile);

            using (StreamWriter sw = fi.AppendText())
            {
                ConsoleEventArgs cea = args as ConsoleEventArgs;
                sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "|" + sender.ToString() + "|" + cea.Message);
            }
        }
    }

class Program
    {
        static void Main(string[] args)
        {
            // 控制台事件管理者
            ConsoleManager cm = new ConsoleManager();
            // 控制台事件订阅者
            Log log = new Log(cm);

            cm.ConsoleOutput("测试控制台输出事件");
            cm.ConsoleOutput("测试控制台输出事件");
            cm.ConsoleOutput("测试控制台输出事件");

            Console.ReadKey();
        }
    }

  

 事件和委托有神马联系?

经常听人说,委托本质是一个类型,而事件本质是一个特殊的委托类型的实例。最好的办法莫过于通过查看原代码和编译后的IL代码进行分析。

  ① 回顾刚刚的代码,在ConsoleManager类中定义了一个事件成员

public event EventHandler<ConsoleEventArgs> ConsoleEvent;

  EventHandler是.NET框架中提供的一种标准的事件模式.

  ② 下面通过Reflector来查看一下事件ConsoleEvent的IL代码(中间代码),可以更方便地看到这一点:

  首先,查看EventHandler的IL代码,可以看到在C#编译器编译delegate代码时,编译后是成为了一个class。

  其次当C#编译器编译event代码时首先为类型添加一个EventHandler<T>的委托实例对象,然后为其增加一对add/remove方法用来实现从委托链中添加和移除方法的功能。

  通过查看add_ConsoleEvent的IL代码,可以清楚地看到订阅事件的本质是调用Delegate的Combine方法将事件处理方法绑定到委托链中

 

posted @ 2016-08-13 14:06  王乐  阅读(241)  评论(0编辑  收藏  举报