如何创建<short, Type>字典?INetworkPacketEventHandler扫描代码返回空问题
问题分析与解决方案
核心问题
你的代码返回空集合的主要原因有两个:
- 类型筛选条件错误:
type.IsDefined(typeof(INetworkPacketEventHandler), true)是检查类型是否被该接口作为特性(Attribute)标记,但你实际需要的是实现了该接口的类,这个条件完全不匹配。 - 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
相关产品推荐
相关产品推荐

