C#如何序列化委托绑定的函数信息至XML?
委托序列化与自动绑定的解决方案
直接序列化EventHandler<SkillEventArgs>类型的委托无法在XML中保存方法名并自动关联,硬编码switch-case处理大量函数又不现实,以下是两种可扩展的可行方案:
方案一:自定义序列化属性+反射直接映射
通过新增一个用于XML序列化的字符串属性,替代原委托属性的序列化逻辑,利用反射在序列化时提取方法名,反序列化时重建委托。
代码实现
using System; using System.Xml.Serialization; using System.Reflection; // 自定义技能事件参数 public class SkillEventArgs : EventArgs { public int Damage { get; set; } } public class Skill { // 实际委托属性,标记为不参与XML序列化 [XmlIgnore] public EventHandler<SkillEventArgs> SkillFunction { get; set; } // 用于XML序列化的字符串属性,对应XML中的SkillFunction节点 [XmlElement("SkillFunction")] public string SkillFunctionName { get { // 提取方法的全名(类全名+方法名),避免重名冲突 return SkillFunction != null ? $"{SkillFunction.Method.DeclaringType.FullName}.{SkillFunction.Method.Name}" : null; } set { if (string.IsNullOrWhiteSpace(value)) { SkillFunction = null; return; } // 拆分类名和方法名 var splitIndex = value.LastIndexOf('.'); var className = value.Substring(0, splitIndex); var methodName = value.Substring(splitIndex + 1); // 通过反射获取类型和方法 var targetType = Type.GetType(className); if (targetType == null) throw new InvalidOperationException($"未找到类型:{className}"); var targetMethod = targetType.GetMethod( methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static ); if (targetMethod == null) throw new InvalidOperationException($"类型 {className} 中未找到方法:{methodName}"); // 实例化委托:静态方法传入null,实例方法传入当前实例或目标类实例(根据实际场景调整) var instance = targetMethod.IsStatic ? null : this; SkillFunction = (EventHandler<SkillEventArgs>)Delegate.CreateDelegate( typeof(EventHandler<SkillEventArgs>), instance, targetMethod ); } } // 其他技能属性 public string SkillName { get; set; } }
说明
- 序列化时,XML的
<SkillFunction>节点会存储方法的全名(如YourNamespace.Skill.CriticalStrike),避免不同类的同名方法冲突 - 反序列化时通过反射自动查找方法并绑定委托,无需硬编码
- 若技能方法是其他类的实例方法,需调整
instance的获取逻辑(比如用单例、依赖注入实例)
方案二:自定义特性+全局方法注册表
通过自定义特性标记所有可序列化的技能方法,初始化时扫描程序集构建方法映射字典,反序列化时直接通过字典查找方法,适合大量技能函数的场景。
代码实现
1. 自定义特性
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class SkillFunctionAttribute : Attribute { // 可选:为方法设置别名,简化XML中的存储内容 public string Alias { get; set; } }
2. 标记技能方法
public class SkillEffectLibrary { [SkillFunction] public void CriticalStrike(object sender, SkillEventArgs e) { e.Damage = (int)(e.Damage * 1.5); } [SkillFunction(Alias = "FireBurst")] public void FireballExplosion(object sender, SkillEventArgs e) { e.Damage += 50; } // 更多技能方法... }
3. 全局方法注册表
using System; using System.Collections.Generic; using System.Reflection; using System.Linq; public static class SkillFunctionRegistry { public static readonly Dictionary<string, MethodInfo> FunctionMap = new Dictionary<string, MethodInfo>(StringComparer.OrdinalIgnoreCase); static SkillFunctionRegistry() { // 扫描当前程序集所有带SkillFunctionAttribute的方法 var allMethods = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(asm => asm.GetTypes()) .SelectMany(type => type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) .Where(method => method.GetCustomAttribute<SkillFunctionAttribute>() != null); foreach (var method in allMethods) { var attr = method.GetCustomAttribute<SkillFunctionAttribute>(); // 使用别名或方法全名作为字典键 var key = string.IsNullOrEmpty(attr.Alias) ? $"{method.DeclaringType.FullName}.{method.Name}" : attr.Alias; if (!FunctionMap.ContainsKey(key)) FunctionMap.Add(key, method); } } }
4. 修改Skill类的序列化逻辑
public class Skill { [XmlIgnore] public EventHandler<SkillEventArgs> SkillFunction { get; set; } [XmlElement("SkillFunction")] public string SkillFunctionName { get { if (SkillFunction == null) return null; var attr = SkillFunction.Method.GetCustomAttribute<SkillFunctionAttribute>(); // 优先返回别名,无别名则返回方法全名 return attr != null && !string.IsNullOrEmpty(attr.Alias) ? attr.Alias : $"{SkillFunction.Method.DeclaringType.FullName}.{SkillFunction.Method.Name}"; } set { if (string.IsNullOrWhiteSpace(value) || !SkillFunctionRegistry.FunctionMap.TryGetValue(value, out var targetMethod)) { SkillFunction = null; return; } // 获取方法实例:静态方法传入null,实例方法需获取目标类实例(根据实际场景调整) object instance = targetMethod.IsStatic ? null : new SkillEffectLibrary(); SkillFunction = (EventHandler<SkillEventArgs>)Delegate.CreateDelegate( typeof(EventHandler<SkillEventArgs>), instance, targetMethod ); } } public string SkillName { get; set; } }
说明
- 新增技能方法时只需添加
[SkillFunction]特性,无需修改序列化逻辑,扩展性极强 - 支持别名,可让XML中的方法名更简洁
- 注册表在程序启动时初始化一次,后续反序列化直接查询字典,性能更优
注意事项
- 技能方法的签名必须严格匹配
EventHandler<SkillEventArgs>(返回void,参数为object和SkillEventArgs),否则Delegate.CreateDelegate会抛出异常 - 若方法是私有方法,反射时需添加
BindingFlags.NonPublic - 实例方法的实例获取逻辑需根据项目架构调整(比如用单例池、依赖注入容器获取)
- 若担心反射性能,可提前缓存委托实例,而非每次反序列化都创建新委托
内容的提问来源于stack exchange,提问作者user19042602
相关产品推荐
相关产品推荐

