使用YamlDotNet反序列化时,如何防止重复设置属性并触发报错?
如何让YamlDotNet在遇到重复属性时抛出异常
我完全懂你的困扰——配置文件里出现重复属性本来就不符合预期,YamlDotNet默认的覆盖行为确实会导致难以排查的问题。要实现第二次设置同一属性时抛出异常,我们可以通过自定义YamlDotNet的属性处理逻辑来实现,下面是具体的方案:
实现思路
核心思路是跟踪每个对象的属性设置状态,当尝试设置一个已经被赋值过的属性时,直接抛出InvalidOperationException。我们可以通过自定义PropertyDescriptor和对应的工厂类,替换YamlDotNet默认的属性处理逻辑来实现。
具体步骤
1. 创建属性跟踪工具类
首先我们需要一个工具类来安全地跟踪每个对象的已设置属性,避免内存泄漏:
using System.Collections.Generic; using System.Runtime.CompilerServices; public static class PropertyTracking { private static readonly ConditionalWeakTable<object, HashSet<string>> _trackedProperties = new(); public static bool IsPropertySet(object obj, string propertyName) { return _trackedProperties.TryGetValue(obj, out var properties) && properties.Contains(propertyName); } public static void MarkPropertyAsSet(object obj, string propertyName) { if (!_trackedProperties.TryGetValue(obj, out var properties)) { properties = new HashSet<string>(); _trackedProperties.Add(obj, properties); } properties.Add(propertyName); } }
2. 自定义抛出异常的PropertyDescriptor
接下来创建一个自定义的PropertyDescriptor,继承自YamlDotNet的ReflectionPropertyDescriptor,重写SetValue方法来添加重复检查:
using System.Reflection; using System; using YamlDotNet.Serialization; public class ThrowingOnDuplicatePropertyDescriptor : ReflectionPropertyDescriptor { public ThrowingOnDuplicatePropertyDescriptor(IPropertyDescriptor baseDescriptor) : base( baseDescriptor.PropertyInfo, baseDescriptor.Type, baseDescriptor.Name, baseDescriptor.CanWrite, baseDescriptor.CanRead, baseDescriptor.SerializationOrder, baseDescriptor.ScalarStyle, baseDescriptor.DefaultValue, baseDescriptor.Alias, baseDescriptor.ApplyNamingConvention ) { } public override void SetValue(object target, object value) { if (PropertyTracking.IsPropertySet(target, Name)) { throw new InvalidOperationException($"Property '{Name}' has already been set on object of type '{target.GetType().FullName}'. Duplicate properties are not allowed."); } base.SetValue(target, value); PropertyTracking.MarkPropertyAsSet(target, Name); } }
3. 自定义PropertyDescriptor工厂类
然后创建一个工厂类,用来生成我们自定义的PropertyDescriptor:
using System.Reflection; using YamlDotNet.Serialization; public class ThrowingOnDuplicatePropertyDescriptorFactory : IPropertyDescriptorFactory { private readonly IPropertyDescriptorFactory _innerFactory; public ThrowingOnDuplicatePropertyDescriptorFactory(IPropertyDescriptorFactory innerFactory) { _innerFactory = innerFactory; } public IPropertyDescriptor Create(PropertyInfo propertyInfo) { var baseDescriptor = _innerFactory.Create(propertyInfo); return new ThrowingOnDuplicatePropertyDescriptor(baseDescriptor); } }
4. 配置Deserializer使用自定义逻辑
最后,在构建Deserializer的时候,替换默认的IPropertyDescriptorFactory:
var deserializer = new DeserializerBuilder() .WithPropertyDescriptorFactory(inner => new ThrowingOnDuplicatePropertyDescriptorFactory(inner)) .Build(); // 测试反序列化你的YAML片段 var yaml = @" bill-to: &id001 street: |- 123 Tornado Alley Suite 16 city: East Westville city: West Westville state: KS "; // 对应的实体类示例 public class Config { public BillTo BillTo { get; set; } } public class BillTo { public string Street { get; set; } public string City { get; set; } public string State { get; set; } } try { var result = deserializer.Deserialize<Config>(yaml); } catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); // 会输出:Property 'city' has already been set on object of type 'BillTo'. Duplicate properties are not allowed. }
注意事项
- 这个方案会对所有属性生效,如果你只需要针对特定属性检查,可以在
ThrowingOnDuplicatePropertyDescriptor的SetValue方法里添加属性名或者自定义特性判断。 - 对于值类型属性(比如
int、bool),如果默认值和业务场景的有效值重合,可能会被误判为已设置。你可以在PropertyTracking的标记逻辑里添加额外判断,只跟踪非默认值的赋值操作。
内容的提问来源于stack exchange,提问作者damageboy
相关产品推荐
相关产品推荐

