事件

事件基于委托,为委托提供了一种发布/订阅机制。在.Net架构内到处都能看到事件。在Windows 应用程序中,Button类提供了Click事件。这类事件就是委托。触发Click事件时调用的处理程序方法需要得到 定义,而其参数由委托类定义。

下列示例代码中,事件用于连接CarDealer类和Comsumer类。CarDealer类提供了一个新车到达时触发的事件。Cunsumer类订阅该事件,以获得新车到达的通知。

事件发布程序

从CarDealer类开始介绍,它基于事件提供一个订阅。CarDealer类用event关键字定义了类型为EventHandler<CarInfoEventArgs>的NewCarInfo事件。在NewCar()方法中,通过调用委托的Invoke方法触发NewCarInfo事件。这个方法的实现确认委托是否为空,如果不为空,就引发事件(使用了空值条件运算符"?.")。

using System;

namespace 事件
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }

    public class CarInfoEventArgs:EventArgs{
        public string Car{get;}
        public CarInfoEventArgs(string car)=>Car = car;
    }
    public class CarDealer{
        public event EventHandler<CarInfoEventArgs> NewCarInfo;
        public void NewCar(string car){
            System.Console.WriteLine($"CarDealer,new car {car}");
            NewCarInfo?.Invoke(this,new CarInfoEventArgs(car));
        }
    }
}

CarDealer类提供了EventHandler<CarInfoEventArgs>类型的NewCarInfo事件。作为一个约定,事件一般使用带两个参数的方法:其中第一个参数是一个对象,包含事件的发送者,第二个参数提供了事件的相关信息。第二个参数随不同的事件类型而改变。.Net 1.0为所有不同的数据类型的事件定义了几百个委托。有了泛型委托EventHandler<T>后,就不再需要委托了。EventHandler<TEventArgs>定义了一个处理程序,它返回void,接受两个参数。对于EventHandler<TEventArgs>,第一个参数必须是object类型,第二个参数是T类型。EvnetHandler<TEventArgs>还定义了一个关于T的约束:它必须派生自基类EventArgs,CarInfoEventArgs就派生自基类EventArgs:

public event EventHandler<CarInfoEventArgs> NewCarInfo;

委托EventHandler<TEventArgs>的定义如下:

        public delegate void EventHander<TEventArgs>(object sender,TEventArgs e)
        where TEventArgs:EventArgs;

在一行上定义事件是C#的简化记法。编译器会创建一个EventHandler<CarInfoEventArgs>的变量,并添加方法,以便从委托中订阅和取消订阅。该简化记法的较长形式如下所示。这非常类似于自动属性和完整属性之间的关系。对于事件,使用add和remove关键字添加和删除委托的处理程序。

private EventHandler<CarInfoEventArgs> _newCarInfo;
public event EventHandler<CarInfoEventArgs> NewCarInfo{
    add=> _newCarInfo += value;
    remove=>_newCarInfo -= value;
}
//如果不仅需要添加和删除事件处理程序,定义事件的长记法就很有用,例如,需要为多个线程访问添加同步操作。WPF控件使用长记法给事件添加冒泡和隧道功能。

CarDealer类通过调用委托的Invoke方法触发事件。可以调用给事件订阅的所有处理程序。注意,与之前的多播委托一样,方法的调用顺序无法保证。为了更多地控制处理程序的调用,可以使用Delegate类的GetInvocation()方法,访问委托列表中的每一项,并独立地调用每个方法。

NewCarInfo?.Invoke(this,new CarInfoEventArgs(car));

触发事件是只包含一行代码的程序。然而,这只是C# 6的功能。在C# 6版本之前,触发事件会更复杂。

以下代码是C# 6之前实现的相同功能。在触发事件之前,需要检查事件是否为空。因为在进行null检查和触发事件之间,可以使用另一个线程把事件设置为null,所以使用一个局部变量,如下所示:

 EventHandler<CarInfoEventArgs> newCarInfo = NewCarInfo;
 if(newCarInfo != null){
   newCarInfo(this,new CarInfoEventArgs(car));
}

在C# 6中,所有这一切都可以使用null传播运算符和一个代码行取代,如前所示。

在触发事件之前,需要检查委托NewCarInfo是否为空。如果没有订阅处理程序,委托就为空:

protected virtual void RaiseNewCarInfo(string car){
            NewCarInfo?.Invoke(this,new CarInfoEventArgs(car));

        }

事件侦听器

Consumer类用作事件侦听器。这个类订阅了CarDealer类的事件,并定义了NewCarIsHere方法,该方法满足EventHandler<CarInfoEventArgs>委托的要求,该委托的参数类型是object和CarInfoEventArgs。

public class Consume{
        private string _name;
        public Consume(string name)=>_name = name;
        public void NewCarIsHere(object sender,CarInfoEventArgs e){
            System.Console.WriteLine($"{_name}: car {e.Car} is new");
        }
    }

现在需要连接事件发布程序和订阅器。为此使用CarDealer类的NewCarInfo事件,通过"+="创建一个订阅。消费者Valtteri订阅了事件,接着消费者Max也订阅了事件,然后Valtteri通过"-="取消了订阅。

        static void Main(string[] args)
        {
            var dealer = new CarDealer();
            var valtteri = new Consume("Valtteri");
            dealer.NewCarInfo += valtteri.NewCarIsHere;
            dealer.NewCar("Williams");
            //Console.WriteLine("Hello World!");

            var max = new Consume("Max");
            dealer.NewCarInfo += max.NewCarIsHere;
            dealer.NewCar("Mercedes");
            dealer.NewCarInfo -= valtteri.NewCarIsHere;
            dealer.NewCar("Feffari");
        }

输出:

运行应用程序,一辆Williams汽车到达,Valtteri得到了通知。因为之后Max也注册了该订阅,所以Valtteri和Max都获得了新款Mercedes汽车的通知。接着Valtteri取消了订阅,所以只有Max获得了Ferrari汽车的通知。

CarDealer,new car Williams
Valtteri: car Williams is new
CarDealer,new car Mercedes
Valtteri: car Mercedes is new
Max: car Mercedes is new
CarDealer,new car Feffari
Max: car Feffari is new

完整代码:

using System;

namespace 事件
{
    class Program
    {
        static void Main(string[] args)
        {
            var dealer = new CarDealer();
            var valtteri = new Consume("Valtteri");
            dealer.NewCarInfo += valtteri.NewCarIsHere;
            dealer.NewCar("Williams");
            //Console.WriteLine("Hello World!");

            var max = new Consume("Max");
            dealer.NewCarInfo += max.NewCarIsHere;
            dealer.NewCar("Mercedes");
            dealer.NewCarInfo -= valtteri.NewCarIsHere;
            dealer.NewCar("Feffari");
        }
    }

    public class CarInfoEventArgs:EventArgs{
        public string Car{get;}
        public CarInfoEventArgs(string car)=>Car = car;
    }
    public class CarDealer{
        public event EventHandler<CarInfoEventArgs> NewCarInfo;
        

        // public delegate void EventHander<TEventArgs>(object sender,TEventArgs e)
        // where TEventArgs:EventArgs;
        //private EventHandler<CarInfoEventArgs> _newCarInfo;
        // public event EventHandler<CarInfoEventArgs> NewCarInfo{
        //     add=> _newCarInfo += value;
        //     remove=>_newCarInfo -= value;
        // }

        public void NewCar(string car){
            System.Console.WriteLine($"CarDealer,new car {car}");
            NewCarInfo?.Invoke(this,new CarInfoEventArgs(car));
        }       
    }
    public class Consume{
        private string _name;
        public Consume(string name)=>_name = name;
        public void NewCarIsHere(object sender,CarInfoEventArgs e){
            System.Console.WriteLine($"{_name}: car {e.Car} is new");
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值