事件

作者:追风剑情 发布于:2021-1-12 16:07 分类:C#

示例一:自定义事件类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp16
{
    class Program
    {
        //EventHandler已在System命名空间中定义,这里直接使用。
        public event EventHandler RaiseEvent;
        //使用EventHandler的泛型版本,发布自定义事件参数。
        public event EventHandler<CustomEventArgs> RaiseCustomEvent1;
        //直接声明自定义事件与上面的泛型版本等价
        public event CustomEventHandler RaiseCustomEvent2;

        static void Main(string[] args)
        {
            var pub = new Publisher();
            var sub1 = new Subscriber("sub1", pub);
            var sub2 = new Subscriber("sub2", pub);
            pub.DoSomething();
            Console.WriteLine("Press any key to continue...");
            Console.ReadLine();
        }

        //事件发布者
        public class Publisher
        {
            public event EventHandler<CustomEventArgs> RaiseCustomEvent;

            public void DoSomething()
            {
                OnRaiseCustomEvent(new CustomEventArgs("Event triggered"));
            }

            protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
            {
                EventHandler<CustomEventArgs> raiseEvent = RaiseCustomEvent;
                if (raiseEvent != null)
                {
                    e.Message += $" at {DateTime.Now}";
                    //raiseEvent(this, e);
                    //防止raiseEvent为null,可以这样写。
                    raiseEvent?.Invoke(this, e);
                }
            }
        }

        //事件订阅者
        public class Subscriber
        {
            private readonly string _id;

            public Subscriber(string id, Publisher pub)
            {
                _id = id;
                pub.RaiseCustomEvent += HandleCustomEvent;
            }

            void HandleCustomEvent(object sender, CustomEventArgs e)
            {
                Console.WriteLine($"{_id} received this message: {e.Message}");
            }
        }
    }

    //自定义事件数据类
    public class CustomEventArgs : EventArgs
    {
        public string Message { get; set; }

        public CustomEventArgs(string message)
        {
            Message = message;
        }
    }

    //事件是特殊类型的委托
    public delegate void CustomEventHandler(object sender, CustomEventArgs args);
}

运行测试
1111.png

示例二:接口事件与事件访问器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp16
{
    class Program
    {
        static void Main(string[] args)
        {
            Shape shape = new Shape();
            Subscriber1 sub1 = new Subscriber1(shape);
            Subscriber2 sub2 = new Subscriber2(shape);
            shape.Draw();
            Console.WriteLine("Press any key to continue...");
            Console.ReadLine();
        }

        public interface IDrawingObject
        {
            event EventHandler OnDraw;
        }

        public interface IShape
        {
            event EventHandler OnDraw;
        }

        public class Shape : IDrawingObject, IShape
        {
            event EventHandler PreDrawEvent;
            event EventHandler PostDrawEvent;

            object objectLock = new object();

            //通过提供自己的访问器,可以指定两个事件是由类中的同一个事件表示,还是由不同事件表示。
            event EventHandler IDrawingObject.OnDraw
            {
                add
                {
                    lock (objectLock)
                    {
                        PreDrawEvent += value;
                    }
                }
                remove
                {
                    lock (objectLock)
                    {
                        PreDrawEvent -= value;
                    }
                }
            }

            event EventHandler IShape.OnDraw
            {
                add
                {
                    lock (objectLock)
                    {
                        PostDrawEvent += value;
                    }
                }
                remove
                {
                    lock (objectLock)
                    {
                        PostDrawEvent -= value;
                    }
                }
            }

            public void Draw()
            {
                PreDrawEvent?.Invoke(this, EventArgs.Empty);
                Console.WriteLine("Drawing a shape.");
                PostDrawEvent?.Invoke(this, EventArgs.Empty);
            }
        }

        //订阅者1
        public class Subscriber1
        {
            public Subscriber1(Shape shape)
            {
                IDrawingObject d = (IDrawingObject)shape;
                d.OnDraw += d_OnDraw;
            }

            void d_OnDraw(object sender, EventArgs e)
            {
                Console.WriteLine("Sub1 receives the IDrawingObject event.");
            }
        }

        //订阅者2
        public class Subscriber2
        {
            public Subscriber2(Shape shape)
            {
                IShape d = (IShape)shape;
                d.OnDraw += d_OnDraw;
            }

            void d_OnDraw(object sender, EventArgs e)
            {
                Console.WriteLine("Sub2 receives the IShape event.");
            }
        }
    }
}

运行测试
22222.png

注意  EventArgs 类在 Microsoft.NET Framework 类库(FCL)中定义,其实现如下:
[ComVisible(true), Serializable]
public class EventArgs {
   public static readonly EventArgs Empty = new EventArgs();
   public EventArgs() { }
}
可以看出,该类型的实现非常简单,就是一个让其他类型继承的基类型。许多事件都没有附加信息需要传递。定义不需要传递附加数据的事件时,可直接使用 EventArgs.Empty,不用构造新的 EventArgs 对象。

示例——自定义事件类型
// 自定义新邮件事件
internal class NewMailEventArgs : EventArgs {
	private readonly String m_from, m_to, m_subject;
	
	public NewMailEventArgs(String from, String to, String subject) {
		m_from = from; m_to = to; m_subject = subject;
	}
	
	public String From { get { return m_from; } }
	public String To { get { return m_to; } }
	public String Subject { get { return m_subject; } }
}

泛型 System.EventHandler 委托类型的定义如下:
public delegate void EventHandler<TEventArgs>(Object sender, TEventArgs e);

示例——定义事件成员
internal class MailManager {
   public event EventHandler<NewMailEventArgs> NewMail;
   
   protected virtual void OnNewMail(NewMailEventArgs e) {
      // 出于线程安全的考虑,现在将对委托字段的引用复制到一个临时变量中
      EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);
      if (temp != null) temp(this, e);
   }

   // 版本二,调用扩展方法Raise
   protected virtual void OnNewMail(NewMailEventArgs e) {
      e.Raise(this, ref NewMail);
   }

   // 当有新邮件到达时触发事件
   public void SimulateNewMail(String from, String to, String subject) {
      NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
      OnNewMail(e);
   }
}

事件处理方法原型形式:
void MethodName(Object sender, NewMailEventArgs e);

事件模式要求所有事件处理方法的返回类型都是 void。遗憾的是FCL中的一些事件处理程序(比如 ResolveEventHandler)没有遵循 Microsoft 自定的模式。
public delegate Assembly ResolveEventHandler(object sender, ResolveEventArgs args);

为方便起见,可定义扩展方法来封装这个线程安全逻辑。

示例——定义扩展方法
public static class EventArgExtensions {
	public static void Raise<TEventArgs>(this TEventArgs e,
		Object sender, ref EventHandler<TEventArgs> eventDelegate) {
		// 出于线程安全的考虑,现在将对委托字段的引用复制到一个临时变量中
		EventHandler<TEventArgs> temp = Volatile.Read(ref eventDelegate);
		if (temp != null) temp(this, e);
	}
}

事件的登记和移除是线程安全的

显式实现事件

using System;
using System.Collections.Generic;
using System.Threading;

namespace ConsoleApp25
{
    class Program
    {
        static void Main(string[] args)
        {
            TypeWithLotsOfEvents twle = new TypeWithLotsOfEvents();
            //添加一个回调
            twle.Foo += HandleFooEvent;
            //证明确实可行
            twle.SimulateFoo();

            Console.ReadLine();
        }

        private static void HandleFooEvent(object sender, FooEventArgs e)
        {
            Console.WriteLine("Handling Foo Event here...");
        }
    }

    // 这个类的目的是在使用EventSet时,提供
    // 多一点的类型安全性和代码可维护性
    public sealed class EventKey { }

    public sealed class EventSet
    {
        // 该私有字典用于维护EventKey->Delegate映射
        private readonly Dictionary<EventKey, Delegate> m_events =
            new Dictionary<EventKey, Delegate>();

        // 添加EventKey->Delegate映射(如果EventKey不存在),
        // 或者将委托和现有的EventKey合并
        public void Add(EventKey eventKey, Delegate handler)
        {
            Monitor.Enter(m_events);
            Delegate d;
            m_events.TryGetValue(eventKey, out d);
            m_events[eventKey] = Delegate.Combine(d, handler);
            Monitor.Exit(m_events);
        }

        // 从EventKey(如果它存在)删除委托,并且
        // 在删除最后一个委托时删除EventKey->Delegate映射
        public void Remove(EventKey eventKey, Delegate handler)
        {
            Monitor.Enter(m_events);
            // 调用TryGetValue,确保在尝试从集合中删除不存在的EventKey时不会抛出异常
            Delegate d;
            if (m_events.TryGetValue(eventKey, out d))
            {
                d = Delegate.Remove(d, handler);
                //如果还有委托,就设置新的头部(地址),否则删除EventKey
                if (d != null) m_events[eventKey] = d;
                else m_events.Remove(eventKey);
            }
            Monitor.Exit(m_events);
        }

        // 为指定的EventKey引发事件
        public void Raise(EventKey eventKey, Object sender, EventArgs e)
        {
            // 如果EventKey不在集合中,不抛出异常
            Delegate d;
            Monitor.Enter(m_events);
            m_events.TryGetValue(eventKey, out d);
            Monitor.Exit(m_events);

            if (d != null)
            {
                // 由于字典可能包含几个不同的委托类型,
                // 所以无法在编译时构造一个类型安全的委托调用
                // 因此,我调用System.Delegate类型的DynamicInvoke
                // 方法,以一个对象数组的形式向它传递回调方法的参数
                // 在内部,DynamicInvoke会向调用的回调方法查证参数的
                // 类型安全性,并调用方法
                // 如果存在类型不匹配的情况,DynamicInvoke会抛出异常
                d.DynamicInvoke(new Object[] { sender, e });
            }
        }
    }

    // 为这个事件定义从EventArgs派生的类型
    public class FooEventArgs : EventArgs { }

    public class TypeWithLotsOfEvents
    {
        // 定义私有实例字段来引用集合
        // 集合用于管理一组“事件/委托”对
        // 注意:EventSet类型不是FCL的一部分,它是自定义的类型
        private readonly EventSet m_eventSet = new EventSet();

        // 受保护的属性使派生类型能访问集合
        protected EventSet EventSet { get { return m_eventSet; } }

        #region 用于支持Foo事件的代码(为附加的事件重复这个模式)
        // 定义Foo事件必要的成员
        // 2a. 构造一个静态只读对象来标识这个事件
        // 每个对象都有自己的哈希码,以便在对象的集合中查找这个事件委托链表
        protected static readonly EventKey s_fooEventKey = new EventKey();

        // 2b. 定义事件的访问器方法,用于在集合中增删委托
        public event EventHandler<FooEventArgs> Foo
        {
            add { m_eventSet.Add(s_fooEventKey, value); }
            remove { m_eventSet.Remove(s_fooEventKey, value); }
        }

        // 2c. 为这个事件定义受保护的虚方法OnFoo
        protected virtual void OnFoo(FooEventArgs e)
        {
            m_eventSet.Raise(s_fooEventKey, this, e);
        }

        // 2d. 定义将输入转换成这个事件的方法
        public void SimulateFoo() { OnFoo(new FooEventArgs()); }
        #endregion
    }
}

运行测试
11111.png

标签: C#

Powered by emlog  蜀ICP备18021003号-1   sitemap

川公网安备 51019002001593号