NET CLR via C#读书笔记 - 第十一章 事件

本文深入探讨了C#中的事件机制,包括事件的基本概念、定义步骤、触发方式及线程安全处理等内容。同时,通过具体示例展示了如何利用事件进行跨对象通信。

1 事件简述

  事件的本质是委托,事件是回调机制的一种应用。
  定义了事件成员的类型或者类型的实例可以通过其它对象发生了特定的通知。

2 如何定义事件

  事件的定义可以大致分为以下4步:
  ① 定义类型来实现需要发送给事件接收者的附加消息。
  ② 定义事件成员。
  ③ 定义负责引发事件的方法来通知事件的登记对象。
  ④ 定义方法将输入转化为期望事件。

2.1 定义类型来实现需要发送给事件接收者的附加消息

  事件引发时,通常需要向事件接收者传递一些附加信息,这种附加信息一般单独封装为独立的类型,类型一般包含一组私有字段以及提供公开这组字段的公共只读属性或者方法。事实上,按照约定,所有的事件的附加信息都应该从EventArgs派生,且派生类应该用EventArgs结尾,因为我们希望一看就知道这是个事件附加信息参数的类型,而不是其它的类型。EventArgs的定义如下:

public class EventArgs {
    public static readonly EventArgs Empty = new EventArgs();
    public EventArgs() {}
}

  该类有一个静态只读字段Empty,这是一个单例;与String.Empty一样,当我们需要一个空的EventArgs时,应该使EventArgs.Empty,而不是重新去派生一个类型,而该类型实际上什么都不做。
  在此处我们派生一个附加信息的类型,该类型描述了新邮件到来时,我们需要传递附加信息:

class NewMailEventArgs : EventArgs
{
    private readonly string m_From; 		//附加信息  发件人
    private readonly string m_To;	 		//附加信息  收件人
    private readonly string m_Subject; 	//附加信息  主题
 
 	//定义只读属性
    public string From { get { return m_From; } } 
    public string To { get { return m_To; } }
    public string Content { get { return m_Subject; } }
 
    public NewMailEventArgs(string From,string To,string Subject)
    {
        this.m_From = From;
        this.m_To = To;
        this.m_Subject = Subject;
    }
}

2.2 定义事件成员

  C# 使用 event 关键字定义事件,事件类型的可访问性一般都是public,以下在MailManager类中定义了一个NewMail事件:

internal class MailManager{
	public event EventHandler<NewMailEventArgs> NewMail;
	//...省略邮件管理类的其它相关代码
}

  NewMail是一个EventHandler委托(委托又是引用类型),只不过用了event关键字进行了修饰。
  委托是用来包装回调函数的,它的本质就是一个class,回调函数的签名必须与委托的签名一致。一个事件可以有多个处理函数,一个函数就会被包装成一个委托对象,所有的委托对象都保存在NewMail的委托链当中。所以,触发NewMail事件,其实就是遍历其指向的委托对象的委托链,执行每个委托对象所包装的方法。
  关于委托的更多信息请点击此处查看
  EventHandler 是个泛型委托,他的定义如下:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

  所以实际上回调函数的原型如下:

public void FuncName(object sender,NewMailEventArgs e);

  对于参数sender使用object而不是使用对应的具体类型的原因如下:
  ① 主要原因:继承,假如有SmtpMailManager继承了MailManager,那么回调方法的参数应该是SmtpMailManager,而不是MailManager,但事实上SmtpMailManager时继承了实际NewMail的,如果需要由SmtpMailManager引发事件,那么就需要将SmtpMailManager转换为MailManager,由于这一层关系,反正最后都要进行转换,所以直接将类型定义为object。
  ② 次要原因:灵活性,假如有一个类不是从MailManager派生,也可以使用这个委托。

2.3 定义负责引发事件的方法来通知事件的登记对象

2.3.1 基本实现

  代码定义如下:

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);
            }
        }
        //...省略邮件管理类的其它相关代码
    }
	EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);

  之所以这样实现代码 ,主要有以下两个原因:
  ① 如果不使用temp局部变量接收NewMail的引用复制,可能会出现竞态问题,竞态问题在这里的体现是在一个线程中检查完temp != null后,另一个线程可能把该委托移出委托链,这样执行到temp(this, e);时会抛出NullReferenceException异常。
  ② 如果仅使用 EventHandler temp = NewMail;也会出现问题,这个问题可能会由编译器的优化引起,编译器会通过移出局部变量来优化上述代码,此时就和原因①中描述的场景一样了。
  所以最终的用法是 EventHandler temp = Volatile.Read(ref NewMail); Volatile.Read的调用强迫NewMail在语句执行时去读取,不允许编译器进行优化(Ps:作者成书的时间是2012年,发展到今天,编译器已经足够智能,即使用原因2中的写法,编译器也不会去进行优化,不过最稳妥的还是代码示例的写法。)。

2.3.2 扩展方法封装

internal class MailManager
{
	//第二步 定义事件消息
	public event EventHandler<NewMailEventArgs> NewMail;
	
	//第三步 定义负责引发事件的方法来通知事件的登记对象
	protected virtual void OnNewMail(NewMailEventArgs e)
	{
		e.Raise(this, ref NewMail); //第四版  此处写的是m_NewMail
	}
	//...省略邮件管理类的其它相关代码
}

public static class EventArgExtensions
{
    public static void Raise<TEventArgs> (this TEventArgs e,object sender,ref EventHandler<TEventArgs> evevtDelegate)
    {
        // 出于线程安全考虑
        EventHandler<TEventArgs> temp = Volatile.Read(ref evevtDelegate);

        //任何事件定义了对事件的关注就通知他们
        if (temp != null)
        {
            temp(sender, e);
        }
    }
}

2.4 定义方法将输入转化为期望事件

internal class MailManager
{
    //第二步 定义事件消息
    public event EventHandler<NewMailEventArgs> NewMail;

    //第三步 定义负责引发事件的方法来通知事件的登记对象
    protected virtual void OnNewMail(NewMailEventArgs e)
    {
        e.Raise(this, ref NewMail); //第四版  此处写的是m_NewMail
    }

    //第四步 定义方法将输入转化为期望事件
    public void SimulateNewMail(string From,string to,string subject)
    {
        // 构造一个事件消息对象来存储需要通知接受者的信息
        NewMailEventArgs e = new NewMailEventArgs(From, to, subject);
        
        //调用虚方法通知对象事件已发生
        //如果没有重写,那么该方法将通知事件的所有已登记对象
        OnNewMail(e);
    }

    //...省略邮件管理类的其它相关代码
}

3 编译器事件实现浅析

public event EventHandler<NewMailEventArgs> NewMail;

  C#编译器在编译时会进行如下转换:

//1  定义一个私有委托字段并初始化为null
private EventHandler<NewMailEventArgs> NewMail = null;

//2 一个公共add_Xxx方法  Xxx是事件名
public void add_NewMail(EventHandler<NewMailEventArgs> value)
{
	EventHandler<NewMailEventArgs> preHandler;
	EventHandler<NewMailEventArgs> newMail = this.NewMail;
	do{
		preHandler = newMail;
		EventHandler<NewMailEventArgs> newHandler = (EventHandler<NewMailEventArgs>)Delegate.Combine(preHandler,value);
		newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail,newHandler,preHandler);
	}while(newMail != preHandler);
}

//3 一个公共remove_Xxx方法  Xxx是事件名
public void remove_NewMail(EventHandler<NewMailEventArgs> value)
{
	EventHandler<NewMailEventArgs> preHandler;
	EventHandler<NewMailEventArgs> newMail = this.NewMail;
	do{
		preHandler = newMail;
		EventHandler<NewMailEventArgs> newHandler = (EventHandler<NewMailEventArgs>)Delegate.Remove(preHandler,value);
		newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail,newHandler,preHandler);
	}while(newMail != preHandler);
}

  第一步中,构造具有恰当委托类型的字段,该字段是对一个委托头部列表的引用,事件发生时会通知列表的委托,初始化为null,表示开始并无侦听者登记对该事件的关注。侦听者对事件进行登记时,其实是将委托类型的一个实例添加到列表中,反之,注销登记事件就是从列表中移除相关委托实例。
  第二步是调用System.Delegate的Combine方法,它将委托实例添加到委托列表中,返回新的列表头,并存回字段中。
  第三步是调用System.Delegate的Remove方法,它将委托实例从委托列表中删除,返回新的列表头,并存回字段中。

4 设计侦听事件的类型

  第2节中说明的是事件定义类的实现,在第四节是说明对事件进行使用的类型,也就是侦听类型的实现。

internal sealed class Fax
{
	//登记对事件的关注
    public Fax(MailManager mm)
    {
        mm.NewMail += FaxMsg;
    }

	//消息处理
    private void FaxMsg(object sender,NewMailEventArgs e)
    {
        Console.WriteLine("Faxing mail massage:");
        Console.WriteLine(" From={0},To={1},subject={2}",e.From,e.To,e.Content);
    }
	
	//撤销对事件的关注
    public void Unregister(MailManager mm)
    {
        mm.NewMail -= FaxMsg;
    }
}

  以上内容为对《NET CLR via C#(第四版)》第十一章内容的阅读笔记,只记录其中核心部分内容,书中对以上主题还进行详细的代码演示说明,书中还讨论了如何显示实现事件以便于如何减少内存损耗,如有需要,请详细阅读本书第十一章内容,感谢您的阅读。

CLR via C# 第4版 英文PDFKristin, words cannot express how /feel about our life together. cherish our family and all our adventures. I'm filled each day with love for Aidan (age 9)and Grant (age 5), you both have been an inspira- tion to me and have taught me to play and have fun Watching the two of you grow up has been so rewarding and enjoyable for me. am lucky to be able to partake in your lives. love and ap preciate you more than you could ever know Contents at a glance Introduction PART I CLR BASICS CHAPTER 1 The clr's execution model CHAPTER 2 Building, Packaging, Deploying, and Administering Applications and Types 33 chaPTeR 3 Shared Assemblies and Strongly Named Assemblies 65 PART I DESIGNING TYPES CHAPTER 4 Type Fundamentals 91 CHAPTER 5 Primitive, Reference, and Value Types 111 CHAPTER 6 Type and Member Basics 151 CHAPTER 7 Constants and fields 175 chaPTer 8 Methods 181 chaPTer 9 Parameters 209 CHAPTER 10 Properties 227 CHAPTER 11 Events 249 CHAPTER 12 Generics 265 CHAPTER 13 Interfaces 295 PARTⅢ ESSENTIAL TYPES CHAPTER 14 Chars, Strings, and Working with Text 317 CHAPTER 15 Enumerated Types and Bit Flags 361 CHAPTER 16 Arrays 373 CHAPTER 17 Delegates 391 CHAPTER 18 Custom Attributes 421 CHAPTER 19 Nullable value Types 441 PART IV CORE FACILITIES CHAPTER 20 Exceptions and state management 451 CHAPTER 21 The Managed Heap and Garbage Collection 505 CHAPTER 22 CLR Hosting and AppDomains 553 CHAPTER 23 Assembly Loading and reflection 583 CHAPTER 24 Runtime serialization 611 CHAPTER 25 Interoperating with WinRT Components 643 PAR V THREADING ChaPTEr 26 Thread basics 669 CHAPTER 27 Compute-Bound Asynchronous Operations 691 CHAPTER 28 IyO-Bound Asynchronous Operations 727 CHAPTER 29 Primitive thread Synchronization Constructs 757 CHAPTER 30 Hybrid Thread Synchronization Constructs 789 Index 823 Contents at a glance Contents Introduction XX PART CLR BASICS Chapter 1 The Clrs Execution Model 3 Compiling Source Code into Managed Modules Combining managed modules into assemblies Loading the Common Language Runtime 8 Executing Your Assembly's Code 11 IL and∨ erification 16 Unsafe Code The Native Code generator tool: ngen. exe 19 The Framework Class Library 22 The Common Type System The Common Language Specification Interoperability with Unmanaged Code 30 Chapter 2 Building, Packaging, Deploying, and Administering Applications and Types 33 NET Framework Deployment Goals 34 Building Types into a Module 35 Response Fil 36 A Brief Look at metadata 38 What do you think of this book We want to hear from you Microsoft is interested in hearing your feedback so we can continually improve our books and learning resources for you. To participate in a brief online survey, please visit microsoft. com/learning/booksurvey Combining Modules to Form an Assembly 45 Adding Assemblies to a Project by Using the Visual Studio IDE.51 Using the assembly Linker Adding Resource Files to an Assembly 53 Assembly Version Resource Information .54 Version numbers ..58 Culture Simple Application Deployment(Privately deployed Assemblies)...60 Simple Administrative Control(Configuration) 62 Chapter 3 Shared Assemblies and Strongly Named Assemblies 65 Two Kinds of Assemblies, Two Kinds of Deployment 66 Giving an Assembly a Strong Name 67 The global Assembly Cache 72 Building an Assembly That References a Strongly Named Assembly..74 Strongly named assemblies are tamper-Resistant 75 Delayed Signing Privately Deploying Strongly Named Assemblies How the Runtime Resolves Type References 80 Advanced Administrative Control( Configuration) 83 Publisher Policy control 86 PART I DESIGNING TYPES Chapter 4 Type Fundamentals 91 All Types Are Derived from System Object .91 Casting Between Types 93 Casting with the C# is and as Operators Namespaces and assemblies 97 How Things relate at Run time .101 Chapter 5 Primitive, Reference, and Value Types 111 Programming Language Primitive Types 111 Checked and Unchecked Primitive Type Operations 115 Reference Types and value Types 118 Boxing and Unboxing Value Types 124 Changing Fields in a Boxed Value Type by Using Interfaces and Why You Shouldnt Do This) 136 Object Equality and Identity 139 Object hash Codes .142 The dynamic Primitive Type ......144 Chapter 6 Type and member Basics 151 The Different Kinds of Type Members .151 Type visibilit 154 Friend assemblies 154 Member accessibility .156 Static Classes ...158 Partial Classes, Structures, and Interfaces .159 Components, Polymorphism, and Versioning 160 How the CLR Calls Virtual Methods, Properties, and Events 162 Using Type Visibility and Member Accessibility Intelligently...166 Dealing with Virtual Methods When Versioning Types 16 Chapter 7 Constants and Fields 175 Constants 175 Fⅰe|ds ...177 Chapter 8 Methods 181 Instance Constructors and Classes(Reference Types) 181 Instance Constructors and Structures(Value Types) 184 Type Constructors 187 Contents x Operator Overload Methods 191 Operators and Programming Language Interoperability 193 Conversion Operator Methods 195 Extension method 198 Rules and guidelines ....,200 Extending Various Types with Extension Methods 201 The Extension Attribute 203 Partial Methods 204 Rules and guidelines 207 Chapter 9 Parameters 209 Optional and Named Parameters 209 Rules and guidelines 210 The defaultParameter value and optional Attributes 212 Implicitly Typed Local Variabl 212 Passing parameters by reference to a Method 214 Passing a variable Number of arguments to a Method 220 Parameter and Return Type Guidelines 223 Const-nes 224 Chapter 10 Properties 227 Parameterless Properties 227 Automatically Implemented Properties 231 Defining Properties Intelligently 232 Object and collection Initializers 235 Anonymous Type .237 The System. Tuple type 240 Parameterful Properties 242 The performance of calling property accessor Methods 247 Property Accessor Accessibility 248 Generic prop A roperty Access 248
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值