You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

支持[EventPriority]标注且强制校验IsHooked的模板模式替代方案

解决方案

核心矛盾

当前模板模式的冲突点非常明确:

  • 外部API只会识别直接挂载的回调方法上的[EventPriority]特性,要求每个事件的挂载入口是独立的、可独立标注特性的方法
  • 现有模板方法把公共入口放在基类,所有派生类复用同一个入口方法,无法给每个入口单独加元数据;同时要避免每个派生类重复编写IsHooked校验逻辑,防止漏写出bug。

方案1:虚方法重写+静态校验(最小改动,推荐中小项目使用)

只需要对原有代码做3处极小调整,完全保留现有模板方法的校验逻辑,没有冗余重复代码,同时支持每个派生类独立配置优先级:

  • 把基类的事件入口方法标记为virtual,保留原有全部IsHooked校验逻辑,核心业务逻辑依然通过抽象的Impl方法强制派生类实现
  • 派生类重写入口方法时,仅在方法上添加自身需要的[EventPriority]特性,方法实现直接调用基类入口,不需要写任何校验或业务逻辑
  • 加一段简单的启动/单元测试校验,扫描所有事件实现类,避免重写入口时漏写base调用

代码实现

调整后的DayEndingEvent基类:

internal abstract class DayEndingEvent : Event
{  
    // 入口改为虚方法,原有校验逻辑完全保留
    internal virtual void OnDayEnding(object? sender, DayEndingEventArgs e)
    {
        if (IsHooked) OnDayEndingImpl(sender, e);
    }

    protected abstract void OnDayEndingImpl(object? sender, DayEndingEventArgs e);
}

具体事件实现类示例:

internal class PlayerSleepDayEndEvent : DayEndingEvent
{
    // 仅在这里配置当前事件的优先级,不需要写任何校验逻辑
    [EventPriority(Priority = 100)]
    internal override void OnDayEnding(object? sender, DayEndingEventArgs e)
    {
        // 直接调用基类入口,所有校验逻辑自动复用
        base.OnDayEnding(sender, e);
    }

    protected override void OnDayEndingImpl(object? sender, DayEndingEventArgs e)
    {
        // 这里只写真正的业务逻辑,不需要关心IsHooked状态
        Console.WriteLine("执行玩家睡觉当日结算逻辑");
    }
}

说明:C#反射获取重写方法的元数据时,会读取派生类重写方法上标注的所有特性。EventManager把派生类实例的OnDayEnding方法挂载到外部API时,外部API可以正常识别到每个类独立配置的[EventPriority],完全满足外部API的限制要求。

配套的校验逻辑(放到项目启动检查或者单元测试里即可,仅需执行一次):

var allEventTypes = typeof(Event).Assembly.GetTypes()
    .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(DayEndingEvent)));
foreach (var eventType in allEventTypes)
{
    var entryMethod = eventType.GetMethod(nameof(DayEndingEvent.OnDayEnding), 
        BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
    if (entryMethod == null || !entryMethod.IsVirtual || entryMethod.DeclaringType != eventType)
    {
        throw new InvalidOperationException($"事件类型{eventType.Name}未正确重写OnDayEnding入口方法,请按规范添加优先级特性并调用base入口");
    }
}

这个方案整体改动不超过10行原有代码,完全兼容现有所有事件实现,新增事件时的重复代码只有1行base调用,成本极低。


方案2:源生成器方案(零重复代码,推荐事件数量多的项目使用)

如果不想保留那1行base调用,可以写一个简单的C#源生成器,在编译期自动生成所有入口代码,彻底消除重复代码:

  • 编译阶段自动扫描所有继承自事件基类的非抽象实现类
  • 读取类上标注的[EventPriority]配置的优先级参数
  • 自动为分部类生成挂载到外部API的入口方法,在生成的方法上自动打对应参数的[EventPriority]特性
  • 生成的方法内部直接调用基类的统一执行入口,100%复用IsHooked校验逻辑,不存在人为漏写的可能

使用时只需要在具体事件类上标注优先级即可,不需要写任何入口相关代码:

// 仅需在类上标注优先级,入口代码全由生成器自动生成
[EventPriority(Priority = 200)]
internal partial class CropGrowDayEndEvent : DayEndingEvent
{
    protected override void OnDayEndingImpl(object? sender, DayEndingEventArgs e)
    {
        // 仅编写业务逻辑
        Console.WriteLine("执行作物生长当日结算逻辑");
    }
}

避坑提醒

不要为了省基类的改动,直接把入口方法改成抽象方法让每个实现类自己写IsHooked校验。随着项目里事件类型增多,早晚会出现漏写校验导致事件在未激活状态下执行的bug,排查和修复成本远高于上述两个方案的实现成本。

内容的提问来源于stack exchange,提问作者Matheus Leão

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 01:45:42