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

如何创建<short, Type>字典?INetworkPacketEventHandler扫描代码返回空问题

问题分析与解决方案

核心问题

你的代码返回空集合的主要原因有两个:

  1. 类型筛选条件错误:type.IsDefined(typeof(INetworkPacketEventHandler), true) 是检查类型是否被该接口作为特性(Attribute)标记,但你实际需要的是实现了该接口的类,这个条件完全不匹配。
  2. Id属性获取方式错误:Id 是接口定义的实例属性,你直接用 type.GetProperty("Id").GetValue(type) 试图从类型本身获取值,这会抛出异常(因为实例属性需要类的实例才能访问)。

修正方案

方案1:要求实现类将Id定义为静态属性(推荐)

先修改接口的实现类,把Id改为静态属性(确保每个处理程序类型的Id唯一且固定):

public class LoginPacketHandler : INetworkPacketEventHandler
{
    public static int Id => 1; // 静态Id属性
    public Task HandleAsync(INetworkClient client, INetworkPacketReader reader)
    {
        // 处理逻辑
        return Task.CompletedTask;
    }
}

然后修改类型扫描代码:

var handlerTypes = Assembly.GetExecutingAssembly().GetTypes()
    // 筛选:实现了接口、不是接口本身、是具体类
    .Where(type => typeof(INetworkPacketEventHandler).IsAssignableFrom(type) 
                   && !type.IsInterface 
                   && type.IsClass)
    .Select(type => 
    {
        // 获取静态Id属性的值
        var idProperty = type.GetProperty("Id", BindingFlags.Public | BindingFlags.Static);
        if (idProperty == null)
            throw new InvalidOperationException($"类型 {type.Name} 未定义静态Id属性");
        
        short id = (short)idProperty.GetValue(null); // 静态属性传null即可
        return new KeyValuePair<short, Type>(id, type);
    })
    .ToDictionary(kv => kv.Key, kv => kv.Value);

方案2:用特性标记Id(无需修改接口)

如果不想修改接口的实例属性定义,可以自定义一个特性来标记每个处理程序的Id:

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class PacketHandlerIdAttribute : Attribute
{
    public short Id { get; }
    public PacketHandlerIdAttribute(short id) => Id = id;
}

然后在实现类上标记:

[PacketHandlerId(1)]
public class LoginPacketHandler : INetworkPacketEventHandler
{
    public int Id => throw new NotImplementedException(); // 接口要求的实例属性可以留空或抛出异常
    public Task HandleAsync(INetworkClient client, INetworkPacketReader reader)
    {
        // 处理逻辑
        return Task.CompletedTask;
    }
}

对应的扫描代码:

var handlerTypes = Assembly.GetExecutingAssembly().GetTypes()
    .Where(type => typeof(INetworkPacketEventHandler).IsAssignableFrom(type) 
                   && !type.IsInterface 
                   && type.IsClass
                   && type.IsDefined(typeof(PacketHandlerIdAttribute), false))
    .Select(type => 
    {
        var idAttr = type.GetCustomAttribute<PacketHandlerIdAttribute>();
        return new KeyValuePair<short, Type>(idAttr.Id, type);
    })
    .ToDictionary(kv => kv.Key, kv => kv.Value);

方案3:创建实例获取Id(不推荐)

如果必须保留接口的实例Id属性,只能创建类的实例来获取值,但这种方式可能会触发类的构造逻辑,存在潜在风险:

var handlerTypes = Assembly.GetExecutingAssembly().GetTypes()
    .Where(type => typeof(INetworkPacketEventHandler).IsAssignableFrom(type) 
                   && !type.IsInterface 
                   && type.IsClass
                   && !type.IsAbstract)
    .Select(type => 
    {
        // 创建实例(假设有无参构造函数)
        var handler = Activator.CreateInstance(type) as INetworkPacketEventHandler;
        if (handler == null)
            throw new InvalidOperationException($"无法创建 {type.Name} 的实例");
        
        short id = (short)handler.Id;
        return new KeyValuePair<short, Type>(id, type);
    })
    .ToDictionary(kv => kv.Key, kv => kv.Value);

关键注意事项

  • 确保所有实现了INetworkPacketEventHandler的类都在当前执行程序集内,如果在其他程序集,需要替换Assembly.GetExecutingAssembly()为对应的程序集(比如typeof(LoginPacketHandler).Assembly)。
  • 要处理重复Id的情况,可以在ToDictionary前添加检查,避免抛出异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 21:52:54