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

使用YamlDotNet实现嵌套字符串到类的动态反序列化方案咨询

问题解答

1. 是否必须使用反射?

不需要手动编写反射逻辑,YamlDotNet本身已经封装了底层反射操作,你只需要自定义类型解析规则即可,无需自己实现反射遍历属性、实例化类的代码。

2. YamlDotNet有没有对应内置支持?

有专门的内置扩展点覆盖你的需求:

  • INodeTypeResolver 接口:支持根据yaml内的自定义字段(比如你的Type字段)动态匹配对应C#类
  • IYamlTypeConverter 接口:支持自定义属性的反序列化逻辑,可实现跨配置文件的实例引用

3. 简洁实现方案

第一步:定义基础实体结构

先把所有可配置对象、组件统一基类,方便类型匹配:

// 所有可配置游戏对象的基类
public abstract class GameObjectBase
{
    public string Name { get; set; }
    public string DisplayName { get; set; }
    public string Sprite { get; set; }
}

// 组件基类
public abstract class ComponentBase { }

// 炮塔类
public class Turret : GameObjectBase
{
    public int MaxHP { get; set; }
    public int Damage { get; set; }
    public float TurretTurnSpeed { get; set; }
    // 反序列化后会自动赋值为对应子弹实例
    public Bullet Projectile { get; set; }
    // 原始配置的名称仅作为反序列化中间映射字段
    public string ProjectileName { get; set; }
}

// 子弹类
public class Bullet : GameObjectBase
{
    public int Damage { get; set; }
    public List<ComponentBase> Components { get; set; } = new();
}

// 爆炸组件类
public class ExplosionComp : ComponentBase
{
    public string ExplosionType { get; set; }
    public int Damage { get; set; }
    public float Radius { get; set; }
}

第二步:实现动态类型解析器

实现根据Type字段自动匹配对应C#类的逻辑:

public class GameObjectTypeResolver : INodeTypeResolver
{
    // 程序启动时扫描一次所有游戏对象、组件类型做缓存,避免重复反射损耗
    private static readonly Dictionary<string, Type> _typeCache = AppDomain.CurrentDomain.GetAssemblies()
        .SelectMany(asm => asm.GetTypes())
        .Where(t => t.IsSubclassOf(typeof(GameObjectBase)) || t.IsSubclassOf(typeof(ComponentBase)))
        .ToDictionary(t => t.Name, t => t);

    public bool Resolve(NodeEvent nodeEvent, ref Type currentType)
    {
        // 解析到Type字段时,用字段值匹配对应类型
        if (nodeEvent is Scalar scalar && scalar.Value == "Type")
        {
            parser.MoveNext();
            var typeName = (parser.Current as Scalar)?.Value;
            if (!string.IsNullOrEmpty(typeName) && _typeCache.TryGetValue(typeName, out var targetType))
            {
                currentType = targetType;
                return true;
            }
        }
        // 解析Components下的组件时自动匹配对应类型
        if (nodeEvent is MappingStart mappingStart && _typeCache.TryGetValue(mappingStart.Anchor?.Value, out var compType))
        {
            currentType = compType;
            return true;
        }
        return false;
    }
}

第三步:实现跨文件引用转换器

把ProjectileName字符串自动映射为已经加载好的子弹实例:

public class ProjectileReferenceConverter : IYamlTypeConverter
{
    private readonly IReadOnlyDictionary<string, GameObjectBase> _configCache;

    // 传入所有预加载的配置缓存
    public ProjectileReferenceConverter(IReadOnlyDictionary<string, GameObjectBase> configCache)
    {
        _configCache = configCache;
    }

    public bool Accepts(Type type) => type == typeof(Bullet);

    public object ReadYaml(IParser parser, Type type)
    {
        var bulletName = parser.Consume<Scalar>().Value;
        return _configCache[bulletName];
    }

    public void WriteYaml(IEmitter emitter, object value, Type type)
    {
        emitter.Emit(new Scalar(((Bullet)value).Name));
    }
}

第四步:组合反序列化流程

// 1. 预加载所有依赖的配置(先加载子弹、组件配置,再加载炮塔配置)
var configCache = new Dictionary<string, GameObjectBase>();
var allConfigFiles = Directory.GetFiles("你的配置文件目录", "*.yaml");

// 先加载非引用类配置(子弹、组件等)
foreach (var file in allConfigFiles.Where(f => !f.Contains("Turret")))
{
    using var reader = new StreamReader(file);
    var deserializer = new DeserializerBuilder()
        .WithNodeTypeResolver(new GameObjectTypeResolver())
        .IgnoreUnmatchedProperties()
        .Build();
    var configObj = deserializer.Deserialize<GameObjectBase>(reader);
    configCache.Add(configObj.Name, configObj);
}

// 2. 加载炮塔配置,自动完成子弹实例注入
using var turretReader = new StreamReader("Turret.yaml");
var turretDeserializer = new DeserializerBuilder()
    .WithNodeTypeResolver(new GameObjectTypeResolver())
    .WithTypeConverter(new ProjectileReferenceConverter(configCache))
    .IgnoreUnmatchedProperties()
    .Build();
var finalTurret = turretDeserializer.Deserialize<Turret>(turretReader);

补充说明

  • 组件的嵌套反序列化无需额外编码,GameObjectTypeResolver已经支持自动把Components下的ExplosionComp映射为对应类实例
  • 如果需要支持循环引用,可以在反序列化构造时添加.WithAliases()配置
  • 类型缓存仅需程序启动时扫描一次,无额外性能损耗

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.12 12:06:03