C# 시리즈 기사 이벤트

PHPz
풀어 주다: 2017-03-12 15:43:26
원래의
1128명이 탐색했습니다.

파일에 포함된 내용:

  • 디자인 공개이벤트유형

  • 컴파일러가 이벤트를 구현하는 방법

  • 청취 이벤트 유형 설계

  • 이벤트를 명시적으로 구현

이벤트: 정의 이벤트 멤버 유형을 사용하면 특정 일이 발생했음을 다른 객체에 알릴 수 있습니다.

CLR 이벤트 모델 은 개체가 구독하는 알림을 수신하는 콜백 메서드를 호출하는 안전한 방식인 대리자를 기반으로 합니다.

은 이벤트 멤버가 다음 기능을 제공할 수 있도록 유형 요구 사항을 정의합니다.

  1. 메서드는 이벤트에 주의를 등록할 수 있습니다

  2. 이벤트에 대한 주의 등록을 취소할 수 있는 메소드

  3. 이벤트가 발생하면 등록된 메소드에 알림이 전송됩니다

본 글은 이메일로 발송됩니다 프로그램을 예로 적용해보세요. 이메일이 도착하면 사용자는 처리를 위해 이메일을 팩스기나 호출기로 전달하기를 원합니다. 먼저 메인 이벤트를 노출하는 수신 이메일을 수신하도록 MainlManager 유형을 디자인하세요. 다른 유형의 개체(팩스 또는 호출기)는 이벤트에 관심을 등록합니다. 이 이벤트는 MailManager가 새 이메일을 수신할 때 발생하여 이메일이 등록된 각 객체에 배포되도록 하며, 각 객체에는 이메일을 처리하는 고유한 방법이 있습니다.

1.1 노출할 이벤트 유형 설계

1단계: 이벤트 알림 수신자에게 전송해야 하는 모든 추가 정보를 수용할 수 있도록 유형을 정의합니다

이는 type에는 일반적으로 일련의 비공개 필드와 이러한 필드를 노출하는 일부 읽기 전용 공개 속성 이 포함되어 있습니다.


 1 class NewMailEventArgs:EventArgs 2     { 3         private readonly string m_from, m_to, m_subject; 4         public NewMailEventArgs(string from,string to,string subject) 5         { 6             m_from = from; 7             m_to = to; 8             m_subject = subject; 9         }10         public string From { get { return m_from; } }11         public string To { get{ return m_to; } }12         public string Subject { get { return m_subject; } }13     }
로그인 후 복사

2단계: 이벤트 구성원 정의


class MailManager
    {        public event EventHandler<NewMailEventArgs> NewMail;
    }
로그인 후 복사

여기서 NewMail은 이벤트 이름입니다. 이벤트 멤버 유형은 이벤트 알림의 모든 수신자가 해당 대리자 유형과 일치하는 프로토타입의 콜백 메서드를 제공해야 함을 나타내는 EventHandler입니다. 일반 System.EventHandler 대리자 유형은 다음과 같이 정의됩니다.

public 대리자 void EventHandler(Object sender,TEventArgs e);

따라서 메소드 프로토타입은 다음 형식을 가져야 합니다. void MethodName(Object sender,NewMailEventArgs e); 이벤트 모드 에서 모든 이벤트 처리 프로그램이 void인 경우 이벤트가 발생한 후 여러 콜백 메서드가 호출될 수 있지만 모든 메서드의 반환 값을 얻을 수 있는 방법이 없기 때문에 void를 반환해도 콜백 메서드가 반환 값을 갖는 것을 허용하지 않습니다.

3단계: 이벤트 등록 객체에 이벤트를 알리기 위해 이벤트를 트리거하는 메서드를 정의합니다.


 1         /// <summary> 2         /// 定义负责引发事件的方法来通知事件的登记对象,该方法定义在MailManager中 3         /// 如果类是密封的,该方法要声明为私有和非虚 4         /// </summary> 5         /// <param name="e"></param> 6         protected virtual void OnNewMail(NewMailEventArgs e) 7         { 8             //出于线程安全考虑,现在将委托字段的引用复制到一个临时变量中 9             EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);10             if(temp!=null)11             {12                 temp(this, e);13             }14         }
로그인 후 복사
위 메서드는 Volatile.Read( ) 스레드 안전성을 보장하기 위한 방법은 주로 다음 두 가지 상황을 고려합니다.

1. NewMail!=null을 직접 판단하지만 NewMail을 호출하기 전에 다른 스레드가 위임 체인에서 위임을 제거하여 이를 비워 둘 수 있습니다. , 따라서 A(NullReference

Exception) 예외가 발생했습니다.

2. Volatile을 사용하지 않고 임시 변수에 저장할 수도 있습니다. 이론적으로는 가능하지만 컴파일러가 코드를 최적화하고 임시 변수를 제거하면 첫 번째 경우와 동일합니다. .

Volatile.Read를 사용하면 이 호출이 발생할 때 NewMail이 강제로 읽혀지는데, 이는 완벽한 솔루션입니다. 그러나 이는 단일 스레드에서는 발생하지 않습니다

네 번째 단계에서는 입력을 예상 이벤트로 변환하는 방법을 정의합니다


1 public void SimulateNewMail(string from,string to,string subject)2         {3             //构造一个对象来容纳想传给通知接收者的信息4             NewMailEventArgs e = new NewMailEventArgs(from, to, subject);5             //调用虚方法通知对象事件已反生6             //如果没有类型重写该方法7             //我们的对象将通知事件的所有登记对象8             OnNewMail(e);9         }
로그인 후 복사
이 방법은 letter MailManager에 새 메일이 도착했습니다.

1.2 컴파일러가 이벤트를 구현하는 방법

MailManager 클래스에서는 이벤트 멤버 자체를 한 문장으로 정의합니다. public event EventHandler NewMail;

C# 컴파일러는 이를 다음 코드로 변환합니다.


         //一个被初始化为null的私有字段
        private EventHandler<NewMailEventArgs> NewMail = null;        public void add_NewMail(EventHandler<NewMailEventArgs> value)
        {            //通过循环和对CompareExchange的调用,以一种线程安全的方式向事件添加委托            //CompareExchange是把目标操作数(第1参数所指向的内存中的数)            //与一个值(第3参数)比较,如果相等,            //则用另一个值(第2参数)与目标操作数(第1参数所指向的内存中的数)交换
            EventHandler<NewMailEventArgs> prevHandler;
            EventHandler<NewMailEventArgs> newMail = this.NewMail;            do
            {
                prevHandler = newMail;
                EventHandler<NewMailEventArgs> newHandler =
                    (EventHandler<NewMailEventArgs>)Delegate.Combine(prevHandler, value);
                newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, newHandler, prevHandler);
            } while (newMail != prevHandler);
        }        public void remove_NewMail(EventHandler<NewMailEventArgs> value)
        {
            EventHandler<NewMailEventArgs> prevHandler;
            EventHandler<NewMailEventArgs> newMail = this.NewMail;            do
            {
                prevHandler = newMail;
                EventHandler<NewMailEventArgs> newHandler =
                    (EventHandler<NewMailEventArgs>)Delegate.Remove(prevHandler, value);
                newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, newHandler, prevHandler);
            } while (newMail != prevHandler);
        }
로그인 후 복사
이 예에서는 NewMail 이벤트가 공개로 선언되었으므로 추가 및 제거 메소드 접근성이 모두 공개됩니다. , 이벤트 접근성에 따라 이벤트에 대한 주의를 등록 및 등록 취소할 수 있는 코드가 결정됩니다. 그러나 어떤 경우에도 유형 자체만 위의 위임 필드 NewMail에 액세스할 수 있습니다. 위에서 생성된 코드 외에도 컴파일러는 관리되는 어셈블리의 메타데이터에 이벤트 정의 항목도 생성합니다. 일부 플래그와 기본 대리자 유형이 포함되어 있습니다. CLR 자체는 이 메타데이터 정보를 사용하지 않으며 런타임에는 접근자 메서드만 필요합니다.

1.3 设计侦听事件的类型

如何定义一个类型来使用另一个类型提供的事件。以Fax类型为例:


internal class Fax
    {        public Fax(MailManager mm)
        {            //向MailManager的NewMail事件登记我们的回调方法
            mm.NewMail += FaxMsg;
        }        //新邮件到达,MailManager将调用这个方法        //sender表示MailManager对象,便于将信息回传给它        //e表示MailManager对象想传给我们的附加事件信息
        private void FaxMsg(object sender, NewMailEventArgs e)
        {
            Console.WriteLine("Fax 的消息from:{0} to:{1} subject:{2}", e.From, e.To, e.Subject);
        }        /// <summary>
        /// 注销        /// </summary>
        /// <param name="mm"></param>
        public void Unregister(MailManager mm)
        {
            mm.NewMail -= FaxMsg;
        }
    }
로그인 후 복사

 电子邮件应用程序初始化时首先构造MailManager对象,并将对该对象的引用保存到变量中。然后构造Fax对象,并将MailManager对象引用作为实参传递。在Fax构造器中,使用+=登记对NewMail事件的关注。

1.4 显式实现事件

对于System.Windows.Forms.Control类型定义了大约70个事件。每个从Control派生类型创建对象都要浪费大量内存,而大多数我们只关心少数几个事件。如何通过显式实现事件来高效的实现提供了大量事件的类思路如下:

定义事件时:公开事件的每个对象都要维护一个集合(如字典)。集合将某种事件标识符作为健,将委托列表作为值。新对象构造时集合也是空白。登记对一个事件的关注会在集合中查找事件的标识符。如果事件标识符存在,新委托就和这个事件的委托列表合并,否则就添加事件标识符和委托。   

引发事件时:对象引发事件会在集合中查找事件的标识符,如果没有说明没有对象登记对这个事件的关注,所以也没委托需要回调。否则就调用与它关联的委托列表。


 1  public sealed class EventKey { } 2     public sealed class EventSet 3     { 4         //定义私有字典 5         private readonly Dictionary<EventKey, Delegate> m_events = 6             new Dictionary<EventKey, Delegate>(); 7         /// <summary> 8         /// 不存在添加,存在则和现有EventKey合并 9         /// </summary>       10         public void Add(EventKey eventKey,Delegate handler)11         {12             //确保操作唯一13             Monitor.Enter(m_events);14             Delegate d;15             //根据健获取值16             m_events.TryGetValue(eventKey, out d);17             //添加或合并18             m_events[eventKey] = Delegate.Combine(d, handler);19             Monitor.Exit(m_events);20         }21         /// <summary>22         /// 删除委托,在删除最后一个委托时还需删除字典中EventKey->Delegate23         /// </summary>       24         public void Remove(EventKey eventKey,Delegate handler)25         {26             Monitor.Enter(m_events);27             Delegate d;28             //TryGetValue确保在尝试从集合中删除不存在的EventKey时不会抛出异常29             if (m_events.TryGetValue(eventKey,out d))30             {31                 d = Delegate.Remove(d, handler);32                 if(d!=null)33                 {34                     //如果还有委托,就设置新的头部35                     m_events[eventKey] = d;36                 }37                 else38                 {39                     m_events.Remove(eventKey);40                 }41             }42             Monitor.Exit(m_events);43         }44         /// <summary>45         /// 为指定的EventKey引发事件46         /// </summary>       47         public void Raise(EventKey eventKey,Object sender,EventArgs e)48         {49             Delegate d;50             Monitor.Enter(m_events);51             m_events.TryGetValue(eventKey, out d);52             Monitor.Exit(m_events);53             if(d!=null)54             {55                 //利用DynamicInvoke,会向调用的回调方法查证参数的类型安全,56                 //并调用方法,如果存在类型不匹配,就抛异常57                 d.DynamicInvoke(new Object[] { sender, e });58             }59         }60     }
로그인 후 복사

接下来定义类来使用EventSet


 1 public class FooEventArgs : EventArgs { } 2     public class TypeWithLotsOfEvents 3     { 4         //用于管理一组"事件/委托" 5         private readonly EventSet m_eventSet = new EventSet(); 6         //受保护的属性使派生类型能访问集合 7         protected EventSet EventSet { get { return m_eventSet; } } 8         //构造一个静态只读对象来标识这个事件 9         //每个对象都有自己的哈希码,以便在对象的集合中查找这个事件的委托链表10         protected static readonly EventKey s_fooEventKey = new EventKey();11         //定义事件访问器方法,用于在集合中增删委托12         public event EventHandler<FooEventArgs> Foo13         {14             add { m_eventSet.Add(s_fooEventKey, value); }15             remove { m_eventSet.Remove(s_fooEventKey, value); }16         }17         //为这个事件定义受保护的虚方法18         protected virtual void OnFoo(FooEventArgs e)19         {20             m_eventSet.Raise(s_fooEventKey, this, e);21         }22         //定义将输入转换成这个事件的方法23         public void SimulateFoo() { OnFoo(new FooEventArgs()); }24     }
로그인 후 복사

如何使用TypeWithLotsOfEvent,只需按照标准的语法向事件登记即可


 1 static void Main(string[] args) 2         {           
 3             TypeWithLotsOfEvents twle = new TypeWithLotsOfEvents(); 4             twle.Foo += HandleFooEvent; 5             twle.SimulateFoo(); 6             Console.Read(); 7         } 8  9         private static void HandleFooEvent(object sender, FooEventArgs e)10         {11             Console.WriteLine("成功");12         }
로그인 후 복사

 

위 내용은 C# 시리즈 기사 이벤트의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!