C#中在派生类触发基类事件及实现接口事件的方法

七月
七月
发布时间:2026-10-13 | 2人浏览过
原创

在C#面向对象编程中,有时需要在派生类中触发基类定义的逻辑,或处理来自不同接口的同名事件。本文将分别阐述如何利用`protected virtual`方法安全地从派生类引发基类事件,以及如何针对继承的多个具有相同名称事件的接口进行显式事件绑定。

在派生类中引发基类事件

在.NET框架中,事件本质上是特殊的委托。默认情况下,只能在定义它们的类内部调用这些事件,派生类无法直接访问并触发基类声明的事件。如果业务逻辑要求派生类能够间接触发基类事件(例如Windows Forms中的常见模式),需要采用特定的设计模式。

第1步:创建受保护的虚调用方法

目的:提供一个安全的入口点,使派生类能在不破坏封装性的前提下触发事件,同时避免多线程竞争条件。

操作说明:在基类中定义一个protected virtual方法。该方法负责检查事件订阅者是否为空,并将事件转发给已注册的监听器。
核心代码示例:

namespace BaseClassEvents
{
  using System;
  using System.Collections.Generic;

  // 自定义 EventArgs,携带形状面积变更数据
  public class ShapeEventArgs : EventArgs
  {
    private double newArea;

    public ShapeEventArgs(double d)
    {
      newArea = d;
    }
    public double NewArea { get { return newArea; } }
  }

  // 抽象基类
  public abstract class Shape
  {
    protected double area;

    public double Area
    {
      get { return area; }
      set { area = value; }
    }

    // 1. 声明事件
    // 使用泛型 EventHandler<T>,无需单独声明委托类型
    public event EventHandler<ShapeEventArgs> ShapeChanged;

    public abstract void Draw();

    // 2. 提供受保护的虚方法用于派生类调用
    protected virtual void OnShapeChanged(ShapeEventArgs e)
    {
      // 将事件副本赋值给局部变量,防止在此行与下一行之间发生竞态条件(最后订阅者退订后报错)
      EventHandler<ShapeEventArgs> handler = ShapeChanged;
      if (handler != null)
      {
        handler(this, e);
      }
    }
  }
}

第2步:在派生类中覆盖并调用基类方法

目的:让具体的派生类执行自身特有的逻辑后,统一触发基类事件通知订阅者。

操作说明:派生类覆写OnShapeChanged方法。在覆写过程中,先处理派生类特定逻辑,随后必须调用base.OnShapeChanged(e)以触发基类事件。注意:不要在基类中将事件声明为virtual并在派生类中使用override来修改事件本身,这会导致编译器行为不可预测。

public class Circle : Shape
{
    private double radius;
    public Circle(double d) { radius = d; area = 3.14 * radius * radius; }

    public void Update(double d)
    {
      radius = d;
      area = 3.14 * radius * radius;
      // 调用受保护方法触发事件
      OnShapeChanged(new ShapeEventArgs(area));
    }

    protected override void OnShapeChanged(ShapeEventArgs e)
    {
      // 处理圆形特有逻辑...
      // 调用基类方法真正抛出事件
      base.OnShapeChanged(e);
    }

    public override void Draw() { Console.WriteLine("Drawing a circle"); }
}

第3步:订阅与测试事件

目的:验证派生类通过基类接口成功触发了事件。

完整运行示例:

// 容器类,用于管理图形并订阅事件
public class ShapeContainer
{
    List<Shape> _list = new List<Shape>();

    public void AddShape(Shape s)
    {
      _list.Add(s);
      // 订阅基类声明的事件
      s.ShapeChanged += HandleShapeChanged;
    }

    private void HandleShapeChanged(object sender, ShapeEventArgs e)
    {
      Shape s = (Shape)sender;
      Console.WriteLine("Received event. Shape area is now {0}", e.NewArea);
      s.Draw();
    }
}

class Program
{
    static void Main(string[] args)
    {
      ShapeContainer sc = new ShapeContainer();
      Circle c1 = new Circle(54);
      Rectangle r1 = new Rectangle(12, 9); // 假设已有Rectangle实现
      
      sc.AddShape(c1);
      sc.AddShape(r1);

      // 触发更新事件
      c1.Update(57);
      r1.Update(7, 7);

      Console.WriteLine("Press any key to exit.");
      Console.ReadKey();
    }
}

预期输出结果:

    Received event. Shape area is now 10201.86
    Drawing a circle
    Received event. Shape area is now 49
    Drawing a rectangle

实现接口中的同名事件

当一个类继承自多个接口,且这些接口包含同名的event时,隐式实现会导致歧义。此时必须使用显式接口实现(Explicit Interface Implementation)来分别绑定这两个同名事件。这种方法允许类内部定义两个不同的私有/受保护事件,分别响应不同接口的需求。

第1步:定义含有同名事件的接口

假设我们需要一个类既能在绘制前触发事件(IDrawingObject),也能在绘制后触发事件(IShape),且两者均名为OnDraw。

public interface IDrawingObject
{
    // 要求在绘制对象之前触发
    event EventHandler OnDraw;
}

public interface IShape
{
    // 要求在绘制形状之后触发
    event EventHandler OnDraw;
}

第2步:在类中提供显式事件实现

目的:将外部接口的OnDraw请求映射到类内部不同的处理逻辑上。

操作说明:在类内部定义两个独立的事件字段(如PreDrawEvent和PostDrawEvent)。然后通过interfaceName.EventName语法显式实现事件访问器(add和remove),将订阅请求指向对应的内部事件。务必注意线程安全,对事件累加操作使用lock。

public class Shape : IDrawingObject, IShape
{
    // 类内部维护两个不同的事件通道
    event EventHandler PreDrawEvent;
    event EventHandler PostDrawEvent;

    object objectLock = new Object();

    // 显式实现 IDrawingObject 的 OnDraw -> 关联 PreDrawEvent
    event EventHandler IDrawingObject.OnDraw
    {
      add
      {
        lock (objectLock)
        {
          PreDrawEvent += value;
        }
      }
      remove
      {
        lock (objectLock)
        {
          PreDrawEvent -= value;
        }
      }
    }

    // 显式实现 IShape 的 OnDraw -> 关联 PostDrawEvent
    event EventHandler IShape.OnDraw
    {
      add
      {
        lock (objectLock)
        {
          PostDrawEvent += value;
        }
      }
      remove
      {
        lock (objectLock)
        {
          PostDrawEvent -= value;
        }
      }
    }

    // 公共绘图方法,按顺序触发不同接口的事件
    public void Draw()
    {
      // 1. 触发绘制前的信号
      EventHandler handler = PreDrawEvent;
      if (handler != null) handler(this, new EventArgs());

      Console.WriteLine("Drawing a shape.");

      // 2. 触发绘制后的信号
      handler = PostDrawEvent;
      if (handler != null) handler(this, new EventArgs());
    }
}

第3步:客户端订阅与隔离

目的:演示调用方如何通过强制转换获取特定接口的引用,从而只接收到对应阶段的事件。

操作说明:订阅者需要将对象强转为具体的接口类型(IDrawingObject或IShape),进而添加事件处理方法。

public class Subscriber1
{
    public Subscriber1(Shape shape)
    {
      // 仅订阅作为“绘图对象”时的行为
      IDrawingObject d = (IDrawingObject)shape;
      d.OnDraw += (s, e) => Console.WriteLine("Sub1 receives the IDrawingObject event.");
    }
}

public class Subscriber2
{
    public Subscriber2(Shape shape)
    {
      // 仅订阅作为“形状”时的行为
      IShape d = (IShape)shape;
      d.OnDraw += (s, 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();
    }
}

预期输出结果:

  Sub1 receives the IDrawingObject event.
  Drawing a shape.
  Sub2 receives the IShape event.

以上就是C#在派生类中触发基类事件及实现接口事件的详细内容,更多关于C#高级事件处理与多接口实现的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表