EKsumic's Blog

let today = new Beginning();

Click the left button to use the catalog.

OR

C#Event事件

Event事件

想要了解C#事件,先了解→C#委托

委托通常存在两个角色:广播者和订阅者

订阅者——指的是委托声明

广播者——指的调用委托的方法

可以参考 C#委托(上)的多播,

订阅者可以通过+=和-=来控制监听方法的数量。

 


这样的操作,被C#简化成了事件,

一方面是为了简化代码,另一方面是为了防止互相干扰。

 


需要注意的一点:

▢ 事件是一种结构,用于实现广播者/订阅者模型,它只暴露了所需的委托特性的部分子集。

 

如何声明事件?

▢ 最简单的声明方式就是在委托前面加上event关键字

public delegate void PriceChangedHandler(decimal oldPrice,decimal newPrice);

public class Broadcaster
{
     // Event declaration
     public event PriceChangedHandler PriceChanged;
}

▢ Broadcaster类型里面的代码拥有对PriceChanged的完全访问权,在这里就可以把它当作委托。

▢ 而Broadcaster类型之外的代码只能对PriceChanged这个event进行+=和-=操作。

以上两点就是描述的“部分子集”的特性。

 



标准的事件模式

▢ 为编写事件,.NET定义了一个标准的模式
▢ System.EventArgs,一个预定义的框架类,除了静态的Empty属性之外,它没有其它成员。
▢ EventArgs是为事件传递信息的类的基类。

public class PriceChangedEventArgs:System.EventArgs
{
    public readonly decimal LastPrice;
    public readonly decimal NewPrice;
    
    public PriceChangedEventArgs(decimal lastPrice,decimal newPrice)
   {
       LastPrice=lastPrice;
       NewPrice=newPrice;
    }
}

接下来,为事件选择和定义委托

▲返回类型必须是void;

▢ 接收两个参数,第一个参数类型是object,第二个参数类型是EventArgs的子类。第一个参数表示事件的广播者,第二个参数包含需要传递的信息;

▢ 名称必须以EventHandler结尾。

☆ Framework定义了一个满足上述规则的泛型委托System.EventHandler<T>

public delegate void EventHandler<TEventArgs>
   (object source,TEventArgs e) where TEventArgs:EventArgs;

然后,针对选择的的委托定义事件


▢ 方法名必须和事件一致,前面再加上On,接收一个EventArgs参数

public class Stock
{
   . . .
   public event EventHandler<PriceChangedEventArgs> PriceChanged;

   protected virtual void OnPriceChanged(PriceChangedEventArgs e)
  {
      if(PriceChanged!=null)  PriceChanged (this,e);    
  }
}

完整的例子

namespace 事件
{
    public class PriceChangedEventArgs : EventArgs
    {
        public readonly decimal LastPrice;
        public readonly decimal NewPrice;
        public PriceChangedEventArgs(decimal lastPrice,decimal newPrice)
        {
            LastPrice = lastPrice;
            NewPrice = newPrice;
        }
    }

    public class Stock
    {
        string symbol;
        decimal price;
        public Stock(string symbol)
        {
            this.symbol = symbol;
        }

        //定义泛型委托
        public event EventHandler<PriceChangedEventArgs> PriceChanged;

        //用于触发事件
        protected virtual void OnPriceChanged(PriceChangedEventArgs e)
        {
            //this代指Stock这个类本身,因为它是广播者
            //这里的?是一个简写方式,目的是线程安全
            PriceChanged?.Invoke(this, e);
        }

        //定义Price取值和赋值的方法(定义属性)
        public decimal Price
        {
            get { return price; }
            set
            {
                if (price == value) return;
                decimal oldPrice = price;
                price = value;
                OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Stock stock = new Stock("MSFT");
            stock.Price = 120;
            stock.PriceChanged += stock_PriceChanged;//增加相应的事件,以便于我们观察
            stock.Price = 135;//修改Price,导致触发 标准模式的方法,这就说明存在监听
            Console.Read();
        }
        //标准模式
        static void stock_PriceChanged(object sender,PriceChangedEventArgs e)
        {
            if ((e.NewPrice - e.LastPrice)/ e.LastPrice > 0.1M)
            {
                Console.WriteLine("Alert,10% stock price increase!");
            }          
        }
    }
}

//到此为止,你应该明白了C# Winform里面的click方法怎么来的了吧

输出:

Alert,10% stock price increase!

▢ 多线程的场景下,你需要在测试或调用前,把委托赋给一个临时变量,来避免线程相关的错误:

    var temp=PriceChanged;
    if(temp!=null) temp(this,e);

▢ 在C#6.0之后,可以这样写:

    PriceChanged?.Invoke(this,e);

 


 

非泛型的EventHandler

▢ 当事件不携带多余的信息的时候,可以使用非泛型的EventHandler委托。

▢ EventArgs.Empty属性

举例:

public class Stock
    {
        string symbol;
        decimal price;

        public Stock(string symbol) { this.symbol = symbol; }

        public event EventHandler PriceChanged;

        protected virtual void OnPriceChanged(EventArgs e)
        {
            PriceChanged?.Invoke(this, e);
        }

        public decimal Price
        {
            get { return price; }
            set
            {
                if (price == value) return;
                price = value;
                OnPriceChanged(EventArgs.Empty);
            }
        }
    }

 


 

事件访问器

▢ 事件访问器是事件的+=和-=函数的实现,

public event EventHandler PriceChanged;

▢ 编译器会把它转化为:
   ▢ 一个私有的委托字段
   ▢ 一对公共的事件访问器函数(add_PriceChanged和remove_PriceChanged),这两个函数的实现会把+=和-=操作交给私有的委托字段。

▢ 也可以显式的定义事件访问器

大概是这个样子的:

private EventHandler priceChanged;

public event EventHandler PriceChanged
{
    add{priceChanged+=value;}
    remove{priceChanged-=value;}
}


什么时候显式定义事件访问器

▢ 当事件访问器仅仅是另一个广播事件的类的中继。

▢ 当类暴露大量event,但是大部分时候都只有少数的订阅者存在。

▢ 显式实现一个声明了事件的接口。

事件修饰符

▢ virtual,可以被重写;abstract,sealed,static。

public class Foo
{
     public static event EventHandler<EventArgs> StaticEvent;
     public virtual event EventHandler<EventArgs> VirtualEvent;
}


 

This article was last edited at 2020-03-17 19:32:03

* *