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

C#集中式事件声明问题:单例EventRegistry报错排查

问题分析与修复

错误原因

你遇到的编译错误是因为C#不允许在实例字段的初始化器中引用其他非静态实例成员。Publisher1Events是实例属性,它的初始化器直接引用了非静态的Event1事件,而字段初始化操作在类实例构造函数执行前完成,此时Event1还未被初始化,因此编译器报错。

基础修复代码

把字典的初始化逻辑移到私有构造函数中,同时将枚举改为公开(让外部能识别事件类型),就能解决这个问题:

using System;
using System.Collections.Generic;

namespace MyNamespace
{
    class EventRegistry
    {
        private EventRegistry() 
        {
            // 将字典初始化移到构造函数内,此时实例成员已可访问
            Publisher1Events = new Dictionary<Publisher1, EventHandler>
            {
                { Publisher1.Event1, Event1 }
            };
        }

        // 用Lazy<T>简化单例实现,线程安全且延迟初始化,替代手动双重检查锁
        private static readonly Lazy<EventRegistry> _lazyInstance = new Lazy<EventRegistry>(() => new EventRegistry());
        public static EventRegistry GetInstance() => _lazyInstance.Value;

        // 公开枚举,让外部能查看并选择要订阅的事件类型
        public enum Publisher1
        {
            Event1,
        }

        private event EventHandler Event1;

        public Dictionary<Publisher1, EventHandler> Publisher1Events { get; }

        // 提供订阅事件的方法(事件的add/remove是封装的,外部无法直接操作)
        public void Subscribe(Publisher1 eventType, EventHandler handler)
        {
            switch (eventType)
            {
                case Publisher1.Event1:
                    Event1 += handler;
                    break;
            }
        }

        // 提供取消订阅的方法
        public void Unsubscribe(Publisher1 eventType, EventHandler handler)
        {
            switch (eventType)
            {
                case Publisher1.Event1:
                    Event1 -= handler;
                    break;
            }
        }

        // 提供触发事件的方法
        public void TriggerEvent(Publisher1 eventType, object sender, EventArgs e)
        {
            switch (eventType)
            {
                case Publisher1.Event1:
                    Event1?.Invoke(sender, e);
                    break;
            }
        }
    }
}

关键优化说明

  1. 单例简化:用Lazy<T>替代手动双重检查锁,C#原生的Lazy<T>默认线程安全,代码更简洁且不易出错。
  2. 事件操作封装:直接暴露事件的委托引用没有意义,因为事件的add/remove访问器是封装的,必须通过Subscribe/Unsubscribe方法来操作;触发事件也需要统一的TriggerEvent方法。
  3. 枚举公开:将Publisher1改为public,才能让外部订阅者通过枚举识别可用事件,满足你“统一中心查看事件”的需求。

更优方案探讨

如果你是Unity开发场景,推荐使用ScriptableObject作为事件中心,它天然支持全局访问,还能在编辑器中配置事件,无需维护单例:

using UnityEngine;
using System;

[CreateAssetMenu(fileName = "EventChannel", menuName = "Events/Event Channel")]
public class EventChannel : ScriptableObject
{
    public event EventHandler OnEventTriggered;

    public void Raise(object sender, EventArgs e)
    {
        OnEventTriggered?.Invoke(sender, e);
    }
}

使用时只需在资源目录创建对应的EventChannel实例,发布者和订阅者直接引用该资源即可,避免了单例的耦合度高、难以测试等问题。

如果是普通C#项目,也可以考虑用**依赖注入(DI)**将事件注册表注入到需要的类中,替代单例,提升代码的可测试性和灵活性。

内容的提问来源于stack exchange,提问作者Victor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 00:12:50