C# 事件(Event)用法

事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。

C# 中使用事件机制实现线程间的通信。

通过事件使用委托
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。

发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。

订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。

声明事件(Event)
在类的内部声明事件,首先必须声明该事件的委托类型。例如:

public delegate void BoilerLogHandler(string status);
然后,声明事件本身,使用 event 关键字:

// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;
上面的代码定义了一个名为 BoilerLogHandler 的委托和一个名为 BoilerEventLog 的事件,该事件在生成的时候会调用委托。

实例
实例 1
using System;
namespace SimpleEvent
{
using System;
/发布器类/
public class EventTest
{
private int value;

public delegate void NumManipulationHandler();


public event NumManipulationHandler ChangeNum;
protected virtual void OnNumChanged()
{
  if ( ChangeNum != null )
  {
    ChangeNum(); /* 事件被触发 */
  }else {
    Console.WriteLine( "event not fire" );
    Console.ReadKey(); /* 回车继续 */
  }
}


public EventTest()
{
  int n = 5;
  SetValue( n );
}


public void SetValue( int n )
{
  if ( value != n )
  {
    value = n;
    OnNumChanged();
  }
}

}

/订阅器类/

public class subscribEvent
{
public void printf()
{
Console.WriteLine( “event fire” );
Console.ReadKey(); /* 回车继续 */
}
}

/触发/
public class MainClass
{
public static void Main()
{
EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 /
subscribEvent v = new subscribEvent(); /
实例化对象 /
e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /
注册 */
e.SetValue( 7 );
e.SetValue( 11 );
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:

event not fire
event fire
event fire
本实例提供一个简单的用于热水锅炉系统故障排除的应用程序。当维修工程师检查锅炉时,锅炉的温度和压力会随着维修工程师的备注自动记录到日志文件中。

实例 2
using System;
using System.IO;

namespace BoilerEventAppl
{

// boiler 类
class Boiler
{
private int temp;
private int pressure;
public Boiler(int t, int p)
{
temp = t;
pressure = p;
}

  public int getTemp()
  {
     return temp;
  }
  public int getPressure()
  {
     return pressure;
  }

}
// 事件发布器
class DelegateBoilerEvent
{
public delegate void BoilerLogHandler(string status);

  // 基于上面的委托定义事件
  public event BoilerLogHandler BoilerEventLog;

  public void LogProcess()
  {
     string remarks = "O. K";
     Boiler b = new Boiler(100, 12);
     int t = b.getTemp();
     int p = b.getPressure();
     if(t > 150 || t < 80 || p < 12 || p > 15)
     {
        remarks = "Need Maintenance";
     }
     OnBoilerEventLog("Logging Info:\n");
     OnBoilerEventLog("Temparature " + t + "\nPressure: " + p);
     OnBoilerEventLog("\nMessage: " + remarks);
  }

  protected void OnBoilerEventLog(string message)
  {
     if (BoilerEventLog != null)
     {
        BoilerEventLog(message);
     }
  }

}
// 该类保留写入日志文件的条款
class BoilerInfoLogger
{
FileStream fs;
StreamWriter sw;
public BoilerInfoLogger(string filename)
{
fs = new FileStream(filename, FileMode.Append, FileAccess.Write);
sw = new StreamWriter(fs);
}
public void Logger(string info)
{
sw.WriteLine(info);
}
public void Close()
{
sw.Close();
fs.Close();
}
}
// 事件订阅器
public class RecordBoilerInfo
{
static void Logger(string info)
{
Console.WriteLine(info);
}//end of Logger

  static void Main(string[] args)
  {
     BoilerInfoLogger filelog = new BoilerInfoLogger("e:\\boiler.txt");
     DelegateBoilerEvent boilerEvent = new DelegateBoilerEvent();
     boilerEvent.BoilerEventLog += new
     DelegateBoilerEvent.BoilerLogHandler(Logger);
     boilerEvent.BoilerEventLog += new
     DelegateBoilerEvent.BoilerLogHandler(filelog.Logger);
     boilerEvent.LogProcess();
     Console.ReadLine();
     filelog.Close();
  }//end of main

}//end of RecordBoilerInfo
}
当上面的代码被编译和执行时,它会产生下列结果:

Logging info:

Temperature 100
Pressure 12

Message: O. K
目录
事件介绍

事件的命名约定

事件完整的声明如下(像属性)/事件简约声明(filed-like)

使用系统定义好的EventHandler、EventHandler的事件委托

事件模型的6个组成部分

事件支持的设计目标

订阅/取消事件

引发事件

事件模式

事件和委托的区别

事件导致的内存泄漏

有了 委托字段/属性,为什么还需要事件呢?

如何实现接口事件

事件介绍
和委托类似,事件是后期绑定机制。 实际上,事件是建立在对委托的语言支持之上的。
事件是对象用于(向系统中的所有相关组件)广播已发生事情的一种方式。 任何其他组件都可以订阅事件,并在
事件引发时得到通知。

通过订阅事件,还可在两个对象(事件源和事件接收器)之间创建耦合。 需要确保当不再对事件感兴趣时,事件接
收器将从事件源取消订阅。
事件具有以下属性:
1、发行者确定何时引发事件;订户确定对事件作出何种响应。
2、一个事件可以有多个订户。 订户可以处理来自多个发行者的多个事件。
3、没有订户的事件永远也不会引发。
4、事件通常用于表示用户操作,例如单击按钮或图形用户界面中的菜单选项。
5、当事件具有多个订户时,引发该事件时会同步调用事件处理程序。 若要异步调用事件,请参阅 “使用异步
方式调用同步方法”。
6、在 .NET 类库中,事件基于 EventHandler 委托和 EventArgs 基类。

事件的本质是委托字段的一个包装器

这个包装是对委托起到限制作用,防止对象内部的委托实例被外部乱用,

封装的一个重要的功能就是隐藏

事件对外界隐藏了委托实例的大部分功能

事件的命名约定:

事件的委托的命名约定

1、用于声明事件的委托,一般命名为事件名+EventHandler(除非是一个非常通用的事件约束)。

2、事件名+EventHandler的委托参数一般有两个(由win32API 演化而来,历史悠久)

第一个参数:sender是Object类型,表示事件源\事件发送者 。

第二个参数:e是EventArgs类型 EventArgs类表示包含事件数据的类的基类,并提供用于不包含事件数据的事件的值。因为EventArgs类里面只有一个静态的EvetnArgs 类型的Empty字段和默认构造函数。

Empty 是引用型静态字段,初始化后就Null。所以EventArgs 类型就是传递一个空数据。

一个事件发生后若要传递附加的参数信息,就需要定义事件参数类,需要继承EventArgs,否则就直接使用EventArgs.Empty即可。(EventArgs.Empty实际上就是new EventArgs())

所传递的消息,举个例子:如果手机铃声响力了(通知),手机拥有者可以根据

手机铃声判断,是短线、电话、闹钟,然后采集不同的处理方式。这响铃(名词)就是事件参数,这不同响铃就是 事件参数所包含的信息。

事件参数的命名约定

事件参数标准 是派生EventAgrs,一般类命名为事件名+EventAgrs,参数名e ,.net core 中这条约定不在强制要求。

事件的命名约定

带动时态的谓词或谓词短语来命名事件。

例如,窗口关闭之前引发的事件称为 Closing,窗口关闭之后引发的事件称为 Closed。

事件触发器的命名约定

On+事件名,即“因何引发”、“事出有因”

访问级别为protected、不能为public、不然又成了借刀杀人的工具

在调用方法里面一定要判断一下委托是否为空或者 public event EventHandlerEvent = delegate { } 确保事件总被初始化的这样就可以不必每次在使用它之前都要检查它是否不为NULL

类的三大功能 存储数据(字段)、处理事情(方法)、通知别人(事件)

事件声明
事件完整的声明如下(像属性):以下订单为例子 自定义一个事件

复制代码
public delegate void OrderEvenHandler(Custemer custemer, OrderEventAgrs e);
private OrderEvenHandler orderEvenHandler= delegate { };//确保事件总被初始化的这样就可以不必每次在使用它之前都要检查它是否不为NULL
public event OrderEvenHandler Order
{
add
{

            this.orderEvenHandler += value;


        }
        remove
        {

            this.orderEvenHandler -= value;

        }

    }

复制代码

事件简约声明(filed-like):

public event OrderEvenHandler Orderw;//像字段声明
系统回自动生成add remove

使用系统定义好的EventHandler、EventHandler的事件委托
EventHandler委托是系统自带的通用性事件委托。

public delegate void EventHandler(object? sender, EventArgs e);
表示将用于处理不具有事件数据的事件的方法。

参数:sender 是Object类型,表示事件源\事件发送者 。
参数:EventArgs 表示事件信息。

所传递的消息,举个例子:如果手机铃声响力了(通知),手机拥有者可以根据

手机铃声判断,是短线、电话、闹钟,然后采集不同的处理方式。这响铃(名词)就是事件参数,这不同响铃就是 事件参数所包含的信息。

.NET Framework 类库中的所有事件均基于 EventHandler 委托,还有泛型版本EventHandler,

事件模型的7个组成部分
1、声明事件的委托、 EventHandler

2、事件成员\声明事件 NameEvent
3、事件拥有者 sender
4、事件参数 EventAgrs
5、触发器 Raise

6、事件订阅者\事件监听器 subsriber\Listener(watcher)
7、订阅–把事件处理器和事件关联在一起,本质上是一种以委托为基础的约定。

事件拥有者

也叫事件源。

事件响应者

也叫事件的订阅者\事件消息的接收者\事件的响应者\事件的处理者\被事件所通知的对象\事件侦听器 都是同一个意思。

一个对象要想被作为事件监听器,需要将该对象中的事件处理程序 注册(或挂载)到另一个能够产生事件的对象(即事件源)

事件处理器

也叫事件处理程序,如果是匿名函数做事件处理器,最好将匿名函数绑定到委托上,在用委托订阅事件,这样可以避免内存泄漏。

触发器

事件只能从类或者派生类触发,不能由外部类触发。所以触发器是protect类型

事件支持的设计目标
事件的语言设计针对这些目标:
在事件源和事件接收器之间启用非常小的耦合。 这两个组件可能不会由同一个组织编写,甚至可能会通
过完全不同的计划进行更新。
订阅事件并从同一事件取消订阅应该非常简单。
事件源应支持多个事件订阅服务器。 它还应支持不附加任何事件订阅服务器。
你会发现事件的目标与委托的目标非常相似。 因此,事件语言支持基于委托语言支持构建。

通过使用 +=/-= 运算符订阅取消事件:
EventHandler onProgress = (sender, eventArgs) =>
Console.WriteLine(eventArgs.FoundFile);
fileLister.Progress += onProgress;

引发事件
引发事件时,使用委托调用语法调用事件处理程序:

Progress?.Invoke(this, new FileListArgs(file));
如委托部分中所介绍的那样,?. 运算符可以轻松确保在事件没有订阅服务器时不引发事件。

事件模式
经典事件模式

.NET框架为事件定义了一个标准模式,它的目的是保持框架和用户代码之间的一致性。

标准事件的模式核心是System.EventArgs——预定义的没有成员的框架类(不同于静态Empty属性)

EventArgs表示包含事件数据的类的基类,并提供用于不包含事件数据的事件的值。用于为事件传递信息的基类。

经典模式案例:

复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

namespace Order
{
class Program
{

    static void Main(string[] args)
    {
        EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>
        {
            Console.WriteLine(eventArgs.FoundFile);
            eventArgs.CancelRequested = true;
        };
        FileSearcher fileLister = new();

        fileLister.DirectoryChanged += (sender, eventArgs) =>
        {
            Console.Write($"Entering '{eventArgs.CurrentSearchDirectory}'.");
            Console.WriteLine($" {eventArgs.CompletedDirs} of {eventArgs.TotalDirs} completed...");
        };
       
    }
}

public class FileFoundArgs : EventArgs
{
    public string FoundFile { get; }
    public bool CancelRequested { get; set; }
    public FileFoundArgs(string fileName)
    {
        FoundFile = fileName;
    }
}
internal class SearchDirectoryArgs : EventArgs
{
    internal string CurrentSearchDirectory { get; }
    internal int TotalDirs { get; }
    internal int CompletedDirs { get; }

    internal SearchDirectoryArgs(string dir, int totalDirs, int completedDirs)
    {
        CurrentSearchDirectory = dir;
        TotalDirs = totalDirs;
        CompletedDirs = completedDirs;
    }
}
public class FileSearcher
{
    public event EventHandler<FileFoundArgs> FileFound;



    public void Search(string directory, string searchPattern, bool searchSubDirs = false)
    {
        if (searchSubDirs)
        {
            var allDirectories = Directory.GetDirectories(directory, "*.*", SearchOption.AllDirectories);
            var completedDirs = 0;
            var totalDirs = allDirectories.Length + 1;
            foreach (var dir in allDirectories)
            {
                directoryChanged?.Invoke(this,
                    new SearchDirectoryArgs(dir, totalDirs, completedDirs++));
                // Search 'dir' and its subdirectories for files that match the search pattern:
                SearchDirectory(dir, searchPattern);
            }
            // Include the Current Directory:
            directoryChanged?.Invoke(this,
                new SearchDirectoryArgs(directory, totalDirs, completedDirs++));
            SearchDirectory(directory, searchPattern);
        }
        else
        {
            SearchDirectory(directory, searchPattern);
        }
    }

    private void SearchDirectory(string directory, string searchPattern)
    {
        foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
        {
            var args = new FileFoundArgs(file);
            FileFound?.Invoke(this, args);
            if (args.CancelRequested)
                break;
        }
    }
    public void List(string directory, string searchPattern)
    {
        foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
        {
            var args = new FileFoundArgs(file);
            FileFound?.Invoke(this, args);
            if (args.CancelRequested)
                break;
        }
    }

    private EventHandler<SearchDirectoryArgs> directoryChanged;
    internal event EventHandler<SearchDirectoryArgs> DirectoryChanged
    {
        add { directoryChanged += value; }
        remove { directoryChanged -= value; }
    }
   
}

}
复制代码

.NET Core 事件模式(不太明白)

1、事件参数作为返回值,事件参数继续遵守经典模式

2、事件参数仅作 传递消息。那么事件参数不用遵守 事件参数命名规则和派生规则。也就是说事件参数可以是不用继承EventArgs类和事件参数可以是结构类型,事件参数类型命名不用以EventArgs结尾。

NET Core 的模式较为宽松。 在此版本中,EventHandler 定义不再要求 TEventArgs 必须是派生自 System.EventArgs 的类。

这就提高了灵活性,并且还具有后向兼容性。 首先讨论灵活性。 类 System.EventArgs 引入了一个方法 MemberwiseClone(),该方法可创建对象的浅表副本。 对于任何派生自 EventArgs 的类,该方法必须使用反射才能实现其功能。 该功能在特定的派生类中更容易创建。 实际上,这意味着派生自 System.EventArgs 的类会限制你的设计,且不会为你提供任何附加好处。 其实,可以更改 FileFoundArgs 和 SearchDirectoryArgs 的定义,使它们不从 EventArgs 派生。 该程序的工作原理相同。

如果还要进行一处更改,还可将 SearchDirectoryArgs 更改为结构:

复制代码
internal struct SearchDirectoryArgs
{
internal string CurrentSearchDirectory { get; }
internal int TotalDirs { get; }
internal int CompletedDirs { get; }

internal SearchDirectoryArgs(string dir, int totalDirs, int completedDirs) : this()
{
    CurrentSearchDirectory = dir;
    TotalDirs = totalDirs;
    CompletedDirs = completedDirs;
}

}
复制代码
其他更改为:在输入初始化所有字段的构造函数之前调用无参数构造函数。 若没有此添加,C# 规则将报告先访问属性再分配属性。

不应将 FileFoundArgs 从类(引用类型)更改为结构(值类型)。 这是因为处理取消的协议要求通过引用传递事件参数。 如果进行了相同的更改,文件搜索类将永远不会观察到任何事件订阅者所做的任何更改。 结构的新副本将用于每个订阅者,并且该副本将与文件搜索对象所看到的不同。

接下来,让我们考虑这种更改如何具有后向兼容性。 删除约束不会影响任何现有代码。 任何现有的事件参数类型仍然派生自 System.EventArgs。 它们将继续从 System.EventArgs 派生,其中一个主要原因就是后向兼容性。 任何现有的事件订阅者都是遵循经典模式的事件的订阅者。

遵循类似的逻辑,现在创建的任何事件参数类型在任何现有代码库中都不会有任何订阅者。 只有从 System.EventArgs 派生的新事件类型才会破坏这些代码库。

异步事件订阅者

还有最后一个模式需要了解:如何正确编写调用异步代码的事件订阅服务器。 该问题详见 async 和 await 一文。 异步方法可具有一个 void 返回类型,但强烈建议不要使用它。 事件订阅者代码调用异步方法时,只能创建 async void 方法。 事件处理程序签名需要该方法。

你需要协调此对立指南。 不管怎样,必须创建安全的 async void 方法。 需要实现的模式的基础知识如下:

复制代码
worker.StartWorking += async (sender, eventArgs) =>
{
try
{
await DoWorkAsync();
}
catch (Exception e)
{
//Some form of logging.
Console.WriteLine($“Async task failure: {e.ToString()}”);
// Consider gracefully, and quickly exiting.
}
};
复制代码

首先请注意,处理程序已被标记为异步处理程序。 因为它将被分配给一个事件处理程序委托类型,所以它将有一个 void 返回类型。 这意味着必须遵循处理程序中显示的模式,并且不允许在异步处理程序上下文之外引发异常。 因为它不返回任务,所以没有任何可通过进入故障状态报告错误的任务。 因为方法是异步的,所以不能简单地引发异常。 (调用方法已继续执行,因为它是 async。)对于不同的环境,实际的运行时行为将有不同的定义。 它可以终止线程或拥有线程的进程,也可以使进程处于不确定状态。 所有这些潜在的结果都非常不理想。

这就是为什么你应该在自己的 try 块中包装异步任务的 await 语句。 如果它的确导致任务出错,则可以记录该错误。 如果它是应用程序无法从中恢复的错误,则可以迅速优雅地退出此程序。

这些就是 .NET 事件模式的主要更新。 你将在所使用的库中看到许多早期版本的示例。 但是,你也应了解最新的模式是什么。

委托和事件的区别
生命周期的区别:

这条是严格意义是不成立的,这边只是根据经验总结

委托经常作为参数传递,他声明周期经常随函数调用完就结束了。

事件是随对象建立后一直存在,直到对象被回收。

使用功能的区别:

1、 对于事件来讲,只能在本类型内部“触发”,外部只能“注册自己+=、注销自己-=“。 事件通常是公共类成员。

委托委托不管在本类型内部还是外部都可以“调用”。并存储为私有类成员(如果它们全部存储)。

2、委托经常使用匿名函数,而事件要尽量避免使用匿名函数,因为匿名函数很难取消订阅。

返回值的区别:

事件没有返回值,委托由返回值

封装

委托是对方法的包装 、事件是对委托的封装

作用域
事件是类的成员,只能在对象或类使用 ,事件是对委托的实例封装;委托是和类同一个级别的,可以在类外声明,委托是是对方法的封装。

事件导致的内存泄漏
问题:事件导致内存泄漏

一个对象要想被作为事件监听器,需要将该对象中的事件处理程序 注册(或挂载)到另一个能够产生事件的对象(即事件源)上,这样会导致事件源必须保持一个到事件侦听器对象的引用,以便在事件发生时调用此侦听器的处理方法。

这很合理,但如果这个引用是一个 强引用,则监听器会作为事件源的一个依赖 从而不能作为垃圾回收,即使引用它的对象是事件源。(即,“+=”右边的事件所属的对象被所挂载的对象所使用,导致其无法释放)

解决方法:

问题解决方案:让事件源可以通过弱引用来引用监听器,在事件源存在时也可以回收监听器对象。

这里有一个标准的模式及其在.NET框架上的实现:弱事件模式(http://msdn.microsoft.com/en-us/library/aa970850.aspx)。

内容来源:https://blog.csdn.net/u013986317/article/details/86236616

有了 委托字段/属性,为什么还需要事件呢?
因为委托可以被外部调用并且被执行,而事情只属于对象本身,只能本对象本身触发。使用事件是为了封装委托功能,让委托使用的更安全。就像属性封装字段一样意思。

如何实现接口事件
接口可以声明事件。 下面的示例演示如何在类中实现接口事件。 这些规则基本上都与实现任何接口方法或属性
时的相同。
在类中实现接口事件
在类中声明事件,然后在相应区域中调用它。

复制代码
namespace ImplementInterfaceEvents
{
public interface IDrawingObject
{
event EventHandler ShapeChanged;
}
public class MyEventArgs : EventArgs
{
// class members
}
public class Shape : IDrawingObject
{
public event EventHandler ShapeChanged;
void ChangeShape()
{
// Do something here before the event…
OnShapeChanged(new MyEventArgs(/arguments/));
}
// or do something here after the event.
}
protected virtual void OnShapeChanged(MyEventArgs e)
{
ShapeChanged?.Invoke(this, e);
}
}
复制代码
下面的示例演示如何处理不太常见的情况:类继承自两个或多个接口,且每个接口都具有相同名称的事件。 在这
种情况下,你必须为至少其中一个事件提供显式接口实现。 为事件编写显式接口实现时,还必须编写 add 和
remove 事件访问器。 通常这些访问器由编译器提供,但在这种情况下编译器不提供它们。
通过提供自己的访问器,可以指定两个事件是由类中的同一个事件表示,还是由不同事件表示。 例如,如果根据
接口规范应在不同时间引发事件,可以在类中将每个事件与单独实现关联。 在下面的示例中,订阅服务器确定它
们通过将形状引用转换为 IShape 或 IDrawingObject 接收哪个 OnDraw 事件。

复制代码
namespace{
usingWrapTwoInterfaceEvents
System;
public interface IDrawingObject
{
// Raise this event before drawing
// the object.
// the object.
event EventHandler OnDraw;
}
public interface IShape
{
// Raise this event after drawing
// the shape.
event EventHandler OnDraw;
}
// Base class event publisher inherits two
// interfaces, each with an OnDraw event
public class Shape : IDrawingObject, IShape
{
// Create an event for each interface event
event EventHandler PreDrawEvent;
event EventHandler PostDrawEvent;
object objectLock = new Object();
// Explicit interface implementation required.
// Associate IDrawingObject’s event with
// PreDrawEvent
#region IDrawingObjectOnDraw
event EventHandler IDrawingObject.OnDraw
{
add
{
lock (objectLock)
{
PreDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PreDrawEvent -= value;
}
}
}
#endregion
// Explicit interface implementation required.
// Associate IShape’s event with
// PostDrawEvent
event EventHandler IShape.OnDraw
{
add
{
lock (objectLock)
{
PostDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PostDrawEvent -= value;
}
}
}
// For the sake of simplicity this one method
// implements both interfaces.
public void Draw()
{
// Raise IDrawingObject’s event before the object is drawn.
PreDrawEvent?.Invoke(this, EventArgs.Empty);
Console.WriteLine(“Drawing a shape.”);
// Raise IShape’s event after the object is drawn.
PostDrawEvent?.Invoke(this, EventArgs.Empty);
}
}
public class Subscriber1
{
// References the shape object as an IDrawingObject
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.”);
}
}
// References the shape object as an IShape
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.”);
}
public class Program
{
static void Main(string[] args)
{
Shape shape = new Shape();
Subscriber1 sub = new Subscriber1(shape);
Subscriber2 sub2 = new Subscriber2(shape);
shape.Draw();
// Keep the console window open in debug mode.
System.Console.WriteLine(“Press any key to exit.”);
System.Console.ReadKey();
}
}
}
/* Output:
Sub1 receives the IDrawingObject event.
Drawing a shape.
Sub2 receives the IShape event.
*/

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光怪陆离的节日

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值