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

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中的方法名更简洁
  • 注册表在程序启动时初始化一次,后续反序列化直接查询字典,性能更优

注意事项

  1. 技能方法的签名必须严格匹配EventHandler<SkillEventArgs>(返回void,参数为object和SkillEventArgs),否则Delegate.CreateDelegate会抛出异常
  2. 若方法是私有方法,反射时需添加BindingFlags.NonPublic
  3. 实例方法的实例获取逻辑需根据项目架构调整(比如用单例池、依赖注入容器获取)
  4. 若担心反射性能,可提前缓存委托实例,而非每次反序列化都创建新委托

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 14:42:22