引言
在复杂的企业级应用中,我们经常需要处理对象图内部的引用关系更新问题。例如,在配置数据去重后,需要将所有指向旧 ID 的引用更新为新 ID;或者在序列化/反序列化过程中,将 JSON 字符串内的旧标识符批量替换。传统做法往往需要编写大量针对特定类型和属性的硬编码逻辑,不仅繁琐易错,而且难以复用。
本文介绍的通用引用管理框架正是为解决此类问题而设计的。它提供了一套高度可扩展的声明式机制,能够在运行时遍历任意复杂的对象图(包括嵌套对象、集合、字典、JSON 字符串),并根据预设规则自动完成引用值的更新。无论是简单的属性重命名,还是需要根据键值查找新对象的复杂映射,该框架都能优雅地应对。
核心概念
在深入代码细节之前,先理解框架的四个核心抽象:
引用目标(ReferenceTarget):一个枚举,表示引用的语义类型(主键、符号或自定义)。它为解析器提供了上下文信息,便于实现不同的解析策略。
引用解析器(IReferenceResolver):框架的执行核心,负责接收旧值,返回新值。开发者通过实现此接口或提供委托来定义具体的替换逻辑。
值访问器(IValueAccessor):对对象图中某一可读写位置的抽象封装。无论是对象属性、字段还是字典的键,都可以通过统一的 GetValue/SetValue 接口进行操作。
引用定义(ReferenceDefinition):描述"何处、何时、如何"应用解析规则。它指定了引用目标、是否递归、是否处理序列化内容,以及可选的筛选条件。
框架组件详解
- 值访问器体系
框架内置了三种 IValueAccessor 实现,覆盖了绝大多数使用场景:
访问器类型 适用场景 读写目标
PropertyAccessor 公共可读写属性 PropertyInfo.SetValue
FieldAccessor 公共实例字段 FieldInfo.SetValue
DictionaryKeyAccessor 字典的键(需支持键替换) 先移除旧条目,再添加新键值对
这种抽象使得遍历器无需关心底层成员的具体类型,极大地简化了遍历逻辑。
- 序列化内容处理器
当引用值被嵌入在 JSON 字符串中时,简单的值替换无法触及内部结构。框架为此设计了 ISerializedContentHandler 接口,并提供了三种常用实现:
JsonContentHandler:支持通过 JSONPath 定位并替换指定节点下的所有字符串值。
JsonArrayContentHandler:将 JSON 数组字符串解析为列表,逐项替换后重新序列化。
JsonObjectContentHandler:递归遍历 JSON 对象,替换所有叶子字符串值。
内部辅助类 JsonReferenceHelper 使用 JToken 实现了递归替换逻辑,保证了对任意复杂 JSON 结构的兼容性。
- 类型引用注册表(TypeReferenceRegistry)
该注册表以类型为键,存储了一组 ReferenceDefinition 实例。它支持全局静态实例(TypeReferenceRegistry.Global),也允许按需创建独立实例。注册表内部使用 ConcurrentDictionary 保证线程安全,并通过快照缓存提高查询性能。
典型的注册操作如下:
csharp
// 为所有 string 类型的属性注册一个基于主键的引用定义
TypeReferenceRegistry.Global.Register(ReferenceTarget.PrimaryKey);
// 为自定义类型注册包含 JSON 处理的复杂定义
var jsonHandler = new JsonContentHandler("$.items[*].id");
TypeReferenceRegistry.Global.Register(
new ReferenceDefinition(ReferenceTarget.PrimaryKey,
isSerializedContent: true,
contentHandler: jsonHandler));
- 全集合遍历器(FullCollectionTraverser)
这是框架中最复杂的组件,负责深度优先遍历对象图。其核心特性包括:
循环引用检测:通过 HashSet 记录已访问对象,防止无限递归。
完整集合支持:正确处理 IEnumerable、IList、IDictionary。
字典键处理:支持对字典键的引用替换,并在键更改后保持值不变。
简单值集合内处理:对于 List 等简单值集合,可直接对其中的字符串应用解析规则。
遍历器在遇到每个成员时,会先查找该成员类型对应的引用定义。若存在匹配的定义,则调用解析器获取新值,并通过访问器写回对象。若定义中标记了 Recurse = true,则对新值继续进行递归遍历。
- 引用更新引擎(ReferenceUpdateEngine)
引擎将解析器、遍历器和注册表组合为一个便捷的外观:
csharp
public class ReferenceUpdateEngine
{
public void Update(object obj);
public void UpdateAll(IEnumerable objects);
}
只需传入根对象,引擎便会自动完成整棵对象图的引用更新。
工作流程
下图展示了框架处理一个对象的完整流程:
高级特性:声明式标记与字段级映射
- 特性标记(ReferenceAttribute)
对于不希望集中管理注册表的场景,框架提供了 [Reference] 特性,允许直接在模型类的属性或字段上声明引用行为:
随后通过扩展方法 ResolveAttributes 即可应用这些规则:
csharp
node.ResolveAttributes(resolver);
- 字段级引用映射机制(FieldMapping 命名空间)
当引用解析需要跨对象查找时,例如根据旧 ID 找到目标对象并提取其新属性值,FieldMappingReferenceResolver 提供了声明式配置能力。
csharp
var mappings = new List
{
new FieldReferenceMapping
{
SourcePropertyPath = "NextNodeKey", // 当前对象的属性路径
TargetPropertyPath = "Callkey", // 目标对象上的属性路径
Mode = FieldReferenceMode.ByValueAsNodeIdentifier
}
};
var nodeLookup = new NodeConfigLookup(id => nodeDictionary.GetValueOrDefault(id));
var resolver = new FieldMappingReferenceResolver(mappings, nodeLookup, renameMap);
该机制支持三种解析模式:
ByValueAsNodeIdentifier:旧值作为节点标识符,查找节点后取其指定属性。
ByValueDirectRename:直接通过重命名映射字典转换旧值。
Custom:提供自定义委托实现任意复杂逻辑。
使用示例
示例 1:全局 ID 重映射
假设我们在配置合并后获得了新旧 ID 的映射字典,现在需要更新整个配置对象图:
csharp
var resolver = new DelegateResolver((oldValue, target, ctx) =>
{
if (oldValue is string id && idMap.TryGetValue(id, out var newId))
return newId;
return oldValue;
});
TypeReferenceRegistry.Global.Register(ReferenceTarget.PrimaryKey);
var engine = new ReferenceUpdateEngine(resolver);
engine.Update(rootConfig);
示例 2:结合 JSON 内容处理
某配置节点的 RawData 属性是一个 JSON 字符串,其中包含待替换的 ID:
csharp
var handler = new JsonContentHandler("$.entities[*].refId");
var definition = new ReferenceDefinition(
ReferenceTarget.PrimaryKey,
isSerializedContent: true,
contentHandler: handler);
TypeReferenceRegistry.Global.Register(definition);
// 使用相同的解析器执行更新
engine.Update(rootNode);
示例 3:使用字段映射更新复杂引用
对于对话树节点,需要将 NextNodeKey 指向的节点的新 Callkey 写入当前属性:
csharp
var builder = new FieldMappingResolverBuilder()
.AddMapping("NextNodeKey", "Callkey", FieldReferenceMode.ByValueAsNodeIdentifier)
.WithNodeLookup(new NodeConfigLookup(id => allNodes.FirstOrDefault(n => n.Callkey == id)))
.WithRenameMaps(renameMap: callkeyRenameMap);
var resolver = builder.Build();
var engine = new ReferenceUpdateEngine(resolver);
engine.Update(dialogueGraph);
总结
通用引用管理框架通过清晰的抽象分层和灵活的扩展点,将原本散落在各处的引用更新逻辑统一为声明式配置。其核心价值在于:
通用性:适用于任何 .NET 对象图,无需修改模型类。
可扩展性:通过实现 IReferenceResolver 或 ISerializedContentHandler 即可支持新的解析策略。
高性能:反射信息缓存、访问器复用、线程安全的并发字典。
易用性:提供全局注册表、特性标记、流畅构建器等多种使用方式,适应不同偏好。
无论是配置数据迁移、序列化内容修复,还是复杂的图数据重连任务,该框架都能显著降低开发成本,提高代码的可维护性。建议将其纳入团队的基础设施库,在遇到引用更新需求时优先考虑使用。
csharp
#region 通用引用管理框架(全集合支持版)
namespace ReferenceManagement
{
#nullable enable
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
// ==================== 核心类型 ====================
public enum ReferenceTarget
{
PrimaryKey,
Symbol,
Custom
}
public class ReferenceContext
{
public object? CurrentObject { get; set; }
public string? Path { get; set; }
public Dictionary<string, object?> CustomData { get; set; } = new();
public object? UserContext { get; set; }
public Type? TargetType { get; set; }
public ReferenceContext Clone()
{
return new ReferenceContext
{
CurrentObject = this.CurrentObject,
Path = this.Path,
UserContext = this.UserContext,
CustomData = new Dictionary<string, object?>(this.CustomData),
TargetType = this.TargetType
};
}
}
public interface IReferenceResolver
{
object? Resolve(object? oldValue, ReferenceTarget target, ReferenceContext context);
}
/// <summary>
/// 将委托包装为 IReferenceResolver 的内部实现。
/// 用于扩展方法中简化调用。
/// </summary>
internal class DelegateResolver : IReferenceResolver
{
private readonly Func<object?, ReferenceTarget, ReferenceContext, object?> _resolveFunc;
public DelegateResolver(Func<object?, ReferenceTarget, ReferenceContext, object?> resolveFunc)
=> _resolveFunc = resolveFunc ?? throw new ArgumentNullException(nameof(resolveFunc));
public object? Resolve(object? oldValue, ReferenceTarget target, ReferenceContext context)
=> _resolveFunc(oldValue, target, context);
}
// ==================== 值访问器(统一读写) ====================
/// <summary>
/// 表示对象图中一个可读写的值位置
/// </summary>
public interface IValueAccessor
{
object? GetValue();
void SetValue(object? newValue);
Type ValueType { get; }
string Description { get; } // 用于调试/日志
}
/// <summary>
/// 属性访问器
/// </summary>
public class PropertyAccessor : IValueAccessor
{
private readonly object _target;
private readonly PropertyInfo _property;
public PropertyAccessor(object target, PropertyInfo property)
{
_target = target;
_property = property;
}
public object? GetValue() => _property.GetValue(_target);
public void SetValue(object? newValue) => _property.SetValue(_target, newValue);
public Type ValueType => _property.PropertyType;
public string Description => $"{_target.GetType().Name}.{_property.Name}";
}
/// <summary>
/// 字段访问器
/// </summary>
public class FieldAccessor : IValueAccessor
{
private readonly object _target;
private readonly FieldInfo _field;
public FieldAccessor(object target, FieldInfo field)
{
_target = target;
_field = field;
}
public object? GetValue() => _field.GetValue(_target);
public void SetValue(object? newValue) => _field.SetValue(_target, newValue);
public Type ValueType => _field.FieldType;
public string Description => $"{_target.GetType().Name}.{_field.Name}";
}
/// <summary>
/// <summary>
/// 字典键访问器,用于在字典遍历过程中支持键的替换。
/// </summary>
/// <remarks>
/// 该类实现了 IValueAccessor 接口,允许在遍历字典时修改字典的键。
/// 当键被修改时,会自动更新字典中的键值对,保持对应的值不变。
/// </remarks>
internal class DictionaryKeyAccessor : IValueAccessor
{
private readonly IDictionary _dict;
private object? _currentKey;
private readonly object _originalKey;
public DictionaryKeyAccessor(IDictionary dict, object key)
{
_dict = dict;
_originalKey = key;
_currentKey = key;
}
public object? GetValue() => _currentKey;
public void SetValue(object? newValue)
{
if (Equals(newValue, _currentKey)) return;
if (newValue == null) return;
var value = _dict[_currentKey];
_dict.Remove(_currentKey);
_currentKey = newValue;
_dict[_currentKey] = value;
}
public Type ValueType => _currentKey?.GetType() ?? typeof(object);
public string Description => $"DictionaryKey[{_originalKey}]";
}
// ==================== 序列化内容处理器 ====================
public interface ISerializedContentHandler
{
string Process(string content, ReferenceTarget target, ReferenceContext context, IReferenceResolver resolver);
}
/// <summary>
/// JSON 引用替换辅助类,提供通用的 JSON Token 引用替换逻辑。
/// </summary>
internal static class JsonReferenceHelper
{
public static JToken ReplaceReferences(JToken token, ReferenceTarget target, ReferenceContext context, IReferenceResolver resolver)
{
return token.Type switch
{
JTokenType.String => new JValue(
resolver.Resolve(token.Value<string>(), target, context)?.ToString()!),
JTokenType.Object => new JObject(
((JObject)token).Properties().Select(p =>
new JProperty(p.Name, ReplaceReferences(p.Value, target, context, resolver)))),
JTokenType.Array => new JArray(
((JArray)token).Select(t => ReplaceReferences(t, target, context, resolver))),
_ => token
};
}
}
public class JsonContentHandler : ISerializedContentHandler
{
private readonly string? _jsonPath;
public JsonContentHandler(string? jsonPath = null) => _jsonPath = jsonPath;
public JsonContentHandler() : this(null) { }
public string Process(string content, ReferenceTarget target, ReferenceContext context, IReferenceResolver resolver)
{
var token = JToken.Parse(content);
var targetToken = string.IsNullOrEmpty(_jsonPath) ? token : token.SelectToken(_jsonPath!);
if (targetToken == null)
throw new InvalidOperationException($"JSON path '{_jsonPath}' not found in content.");
var newToken = JsonReferenceHelper.ReplaceReferences(targetToken, target, context, resolver);
if (targetToken != token)
{
targetToken.Replace(newToken);
return token.ToString(Formatting.None);
}
return newToken.ToString(Formatting.None);
}
}
// ==================== 引用定义(支持任意类型和集合) ====================
/// <summary>
/// 引用定义,描述一个具体的值位置应该如何被解析
/// </summary>
public class ReferenceDefinition
{
public ReferenceTarget Target { get; }
public bool IsSerializedContent { get; }
public ISerializedContentHandler? ContentHandler { get; }
public bool Recurse { get; }
// 可选的筛选器:当访问器满足条件时应用此定义
public Func<IValueAccessor, bool>? Condition { get; set; }
public ReferenceDefinition(ReferenceTarget target, bool isSerializedContent = false,
ISerializedContentHandler? contentHandler = null, bool recurse = true)
{
Target = target;
IsSerializedContent = isSerializedContent;
ContentHandler = contentHandler;
Recurse = recurse;
}
}
// ==================== 类型引用定义存储 ====================
public class TypeReferenceRegistry
{
private readonly ConcurrentDictionary<Type, List<ReferenceDefinition>> _typeDefinitions = new();
private readonly ConcurrentDictionary<Type, List<ReferenceDefinition>> _valueTypeDefinitions = new(); // 值类型(string/int等)
private readonly ConcurrentDictionary<Type, IReadOnlyList<ReferenceDefinition>> _cache = new();
public static TypeReferenceRegistry Global { get; } = new();
/// <summary>
/// 为特定类型注册引用定义(应用于该类型的所有实例成员)
/// </summary>
public void Register<T>(ReferenceDefinition definition) => Register(typeof(T), definition);
public void Register(Type type, ReferenceDefinition definition)
{
if (type == null) throw new ArgumentNullException(nameof(type));
if (definition == null) throw new ArgumentNullException(nameof(definition));
var dict = type.IsValueType || type == typeof(string) ? _valueTypeDefinitions : _typeDefinitions;
var list = dict.GetOrAdd(type, _ => new List<ReferenceDefinition>());
lock (list)
{
list.Add(definition);
_cache.TryRemove(type, out _);
}
}
/// <summary>
/// 为特定类型注册一个简单的引用目标(快捷方式)
/// </summary>
public void Register<T>(ReferenceTarget target, bool isSerializedContent = false,
ISerializedContentHandler? handler = null, bool recurse = true)
=> Register<T>(new ReferenceDefinition(target, isSerializedContent, handler, recurse));
/// <summary>
/// 为值类型或字符串注册引用定义(例如 List<string> 中的每个字符串)
/// </summary>
public void RegisterValue<T>(ReferenceTarget target) where T : struct
=> Register<T>(new ReferenceDefinition(target));
public void RegisterString(ReferenceTarget target) => Register<string>(new ReferenceDefinition(target));
public bool TryGetDefinitions(Type type, out IReadOnlyList<ReferenceDefinition> definitions)
{
if (_cache.TryGetValue(type, out var cached))
{
definitions = cached;
return true;
}
// 先查值类型定义,再查引用类型定义
List<ReferenceDefinition>? list = null;
if ((type.IsValueType || type == typeof(string)) && _valueTypeDefinitions.TryGetValue(type, out list))
{
definitions = CreateSnapshotAndCache(type, list);
return true;
}
else if (_typeDefinitions.TryGetValue(type, out list))
{
definitions = CreateSnapshotAndCache(type, list);
return true;
}
definitions = Array.Empty<ReferenceDefinition>();
return false;
}
private IReadOnlyList<ReferenceDefinition> CreateSnapshotAndCache(Type type, List<ReferenceDefinition> list)
{
lock (list)
{
if (_cache.TryGetValue(type, out var cached))
return cached;
var snapshot = list.ToArray();
_cache[type] = snapshot;
return snapshot;
}
}
}
// ==================== 对象遍历器(全集合支持) ====================
public interface IObjectTraverser
{
void Traverse(object obj, IReferenceResolver resolver, TypeReferenceRegistry registry, ReferenceContext context);
}
/// <summary>
/// 缓存类型的可写属性和字段信息,避免重复反射
/// </summary>
internal static class MemberCache
{
private static readonly ConcurrentDictionary<Type, List<PropertyInfo>> _writableProperties = new();
private static readonly ConcurrentDictionary<Type, List<FieldInfo>> _publicFields = new();
public static IReadOnlyList<PropertyInfo> GetWritableProperties(Type type)
{
return _writableProperties.GetOrAdd(type, t => t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0)
.ToList());
}
public static IReadOnlyList<FieldInfo> GetPublicFields(Type type)
{
return _publicFields.GetOrAdd(type, t => t.GetFields(BindingFlags.Public | BindingFlags.Instance).ToList());
}
}
public class FullCollectionTraverser : IObjectTraverser
{
private readonly HashSet<object> _visited = new(ReferenceEqualityComparer.Instance);
private readonly object _lock = new();
private readonly bool _processDictionaryKeys;
private readonly bool _processSimpleValuesInCollections;
private readonly Dictionary<Type, IReadOnlyList<ReferenceDefinition>> _typeDefinitionsCache = new();
public FullCollectionTraverser(bool processDictionaryKeys = true, bool processSimpleValuesInCollections = true)
{
_processDictionaryKeys = processDictionaryKeys;
_processSimpleValuesInCollections = processSimpleValuesInCollections;
}
/// <summary>
/// 遍历对象图,解析所有引用。
/// </summary>
/// <param name="obj">要遍历的根对象</param>
/// <param name="resolver">引用解析器</param>
/// <param name="registry">类型引用定义注册表</param>
/// <param name="context">引用上下文</param>
/// <remarks>
/// 使用 _visited 集合防止循环引用导致的无限递归。
/// 当对象被访问时添加到集合,处理完成后移除,允许同一对象在不同路径中被多次处理。
/// 使用 _lock 对象确保线程安全。
/// 根对象的路径表示为 "root"。
/// </remarks>
public void Traverse(object obj, IReferenceResolver resolver, TypeReferenceRegistry registry, ReferenceContext context)
{
if (obj == null) return;
lock (_lock)
{
if (!_visited.Add(obj)) return;
}
try
{
// 处理根对象自身的引用定义(注意:根对象没有父访问器,无法替换,仅用于递归解析)
ProcessObjectValue(obj, resolver, registry, context, accessor: null, path: "root");
// 递归遍历成员
TraverseMembers(obj, resolver, registry, context);
}
finally
{
lock (_lock)
{
_visited.Remove(obj);
}
}
}
private void ProcessObjectValue(object value, IReferenceResolver resolver, TypeReferenceRegistry registry,
ReferenceContext context, IValueAccessor? accessor, string? path = null)
{
var type = value.GetType();
if (!registry.TryGetDefinitions(type, out var defs)) return;
var valueContext = context.Clone();
valueContext.CurrentObject = value;
valueContext.Path = path;
valueContext.TargetType = accessor?.ValueType;
object? currentValue = value;
foreach (var def in defs)
{
if (ShouldSkipDefinition(def, accessor)) continue;
var newValue = ResolveValue(currentValue, def, resolver, valueContext, path);
currentValue = UpdateValue(currentValue, newValue, accessor);
if (def.Recurse && currentValue != null && !ReferenceEquals(currentValue, value))
{
Traverse(currentValue, resolver, registry, valueContext);
}
}
}
private bool ShouldSkipDefinition(ReferenceDefinition def, IValueAccessor? accessor)
{
return def.Condition != null && accessor != null && !def.Condition(accessor);
}
private object? ResolveValue(object? currentValue, ReferenceDefinition def, IReferenceResolver resolver, ReferenceContext context, string? path)
{
if (!def.IsSerializedContent)
return resolver.Resolve(currentValue, def.Target, context);
if (def.ContentHandler == null)
throw new InvalidOperationException($"IsSerializedContent is true but ContentHandler is null for path: {path ?? "root"}");
if (currentValue is string strContent)
{
var newContent = def.ContentHandler.Process(strContent, def.Target, context, resolver);
return newContent != strContent ? newContent : currentValue;
}
return resolver.Resolve(currentValue, def.Target, context);
}
private object? UpdateValue(object? currentValue, object? newValue, IValueAccessor? accessor)
{
if (Equals(newValue, currentValue)) return currentValue;
if (accessor != null)
accessor.SetValue(newValue);
return newValue;
}
private void TraverseMembers(object obj, IReferenceResolver resolver, TypeReferenceRegistry registry, ReferenceContext baseContext)
{
if (obj is string) return;
if (obj is IDictionary dict)
{
TraverseDictionary(dict, resolver, registry, baseContext);
return;
}
if (obj is IEnumerable enumerable)
{
TraverseEnumerable(enumerable, resolver, registry, baseContext);
return;
}
TraversePropertiesAndFields(obj, resolver, registry, baseContext);
}
private void TraversePropertiesAndFields(object obj, IReferenceResolver resolver, TypeReferenceRegistry registry, ReferenceContext baseContext)
{
var type = obj.GetType();
// 处理属性
foreach (var prop in MemberCache.GetWritableProperties(type))
{
ProcessMember(obj, prop.Name, new PropertyAccessor(obj, prop), resolver, registry, baseContext);
}
// 处理字段
foreach (var field in MemberCache.GetPublicFields(type))
{
ProcessMember(obj, field.Name, new FieldAccessor(obj, field), resolver, registry, baseContext);
}
}
private void ProcessMember(object obj, string memberName, IValueAccessor accessor, IReferenceResolver resolver, TypeReferenceRegistry registry, ReferenceContext baseContext)
{
var value = accessor.GetValue();
if (value == null) return;
var memberContext = baseContext.Clone();
memberContext.Path = memberName;
ProcessObjectValue(value, resolver, registry, memberContext, accessor, memberName);
var newValue = accessor.GetValue();
if (!ReferenceEquals(value, newValue) && newValue != null)
{
Traverse(newValue, resolver, registry, memberContext);
}
else if (!IsPrimitiveOrString(value.GetType()))
{
Traverse(value, resolver, registry, memberContext);
}
}
private void TraverseEnumerable(IEnumerable enumerable, IReferenceResolver resolver, TypeReferenceRegistry registry, ReferenceContext baseContext)
{
var list = enumerable as IList;
bool isMutable = list != null && !list.IsReadOnly;
int index = 0;
foreach (var item in enumerable)
{
if (item == null) { index++; continue; }
var itemType = item.GetType();
bool isSimple = IsPrimitiveOrString(itemType);
if (isSimple && _processSimpleValuesInCollections)
{
if (!_typeDefinitionsCache.TryGetValue(itemType, out var defs))
{
registry.TryGetDefinitions(itemType, out defs);
_typeDefinitionsCache[itemType] = defs;
}
if (defs.Count > 0)
{
var elemContext = baseContext.Clone();
elemContext.CurrentObject = item;
elemContext.Path = $"[{index}]";
object? currentValue = item;
foreach (var def in defs)
{
var newValue = resolver.Resolve(currentValue, def.Target, elemContext);
if (!Equals(newValue, currentValue))
{
currentValue = newValue;
if (isMutable) list![index] = currentValue;
}
}
}
}
else if (!isSimple)
{
Traverse(item, resolver, registry, baseContext);
}
index++;
}
}
private void TraverseDictionary(IDictionary dict, IReferenceResolver resolver, TypeReferenceRegistry registry, ReferenceContext baseContext)
{
var keys = new System.Collections.ArrayList(dict.Keys);
foreach (var key in keys)
{
if (key == null) continue;
var keyAccessor = new DictionaryKeyAccessor(dict, key);
if (_processDictionaryKeys)
{
var keyType = key.GetType();
if (!_typeDefinitionsCache.TryGetValue(keyType, out var keyDefs))
{
registry.TryGetDefinitions(keyType, out keyDefs);
_typeDefinitionsCache[keyType] = keyDefs;
}
if (keyDefs.Count > 0)
{
ProcessObjectValue(key, resolver, registry, baseContext, keyAccessor, $"Key[{key}]");
}
}
var currentKey = keyAccessor.GetValue();
if (currentKey != null && !IsPrimitiveOrString(currentKey.GetType()))
{
Traverse(currentKey, resolver, registry, baseContext);
}
if (currentKey != null)
{
ProcessDictionaryValue(dict, currentKey, baseContext, resolver, registry);
}
}
}
private void ProcessDictionaryValue(IDictionary dict, object key, ReferenceContext baseContext, IReferenceResolver resolver, TypeReferenceRegistry registry)
{
if (key == null) return;
var value = dict[key];
if (value == null) return;
var valueType = value.GetType();
bool isSimple = IsPrimitiveOrString(valueType);
if (isSimple && _processSimpleValuesInCollections)
{
if (!_typeDefinitionsCache.TryGetValue(valueType, out var valDefs))
{
registry.TryGetDefinitions(valueType, out valDefs);
_typeDefinitionsCache[valueType] = valDefs;
}
if (valDefs.Count > 0)
{
var valContext = baseContext.Clone();
valContext.CurrentObject = value;
valContext.Path = $"Value[{key}]";
object? currentValue = value;
foreach (var def in valDefs)
{
var newValue = resolver.Resolve(currentValue, def.Target, valContext);
if (!Equals(newValue, currentValue))
{
currentValue = newValue;
dict[key] = currentValue;
}
}
}
}
else if (!isSimple)
{
Traverse(value, resolver, registry, baseContext);
}
}
private static bool IsPrimitiveOrString(Type type) => type.IsPrimitive || type == typeof(string);
}
internal class ReferenceEqualityComparer : IEqualityComparer<object>
{
public static ReferenceEqualityComparer Instance { get; } = new();
public bool Equals(object? x, object? y) => ReferenceEquals(x, y);
public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj);
}
// ==================== 引用更新引擎 ====================
public class ReferenceUpdateEngine
{
private readonly IReferenceResolver _resolver;
private readonly IObjectTraverser _traverser;
private readonly TypeReferenceRegistry _registry;
private readonly ReferenceContext _baseContext;
public ReferenceUpdateEngine(
IReferenceResolver resolver,
IObjectTraverser? traverser = null,
TypeReferenceRegistry? registry = null,
object? userContext = null)
{
_resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
_traverser = traverser ?? new FullCollectionTraverser();
_registry = registry ?? TypeReferenceRegistry.Global;
_baseContext = new ReferenceContext { UserContext = userContext };
}
public void Update(object obj)
{
if (obj == null) return;
_traverser.Traverse(obj, _resolver, _registry, _baseContext);
}
public void UpdateAll(IEnumerable<object> objects)
{
foreach (var obj in objects) Update(obj);
}
}
// ==================== 扩展方法 ====================
public static class ReferenceExtensions
{
/// <summary>
/// 使用指定的解析器更新对象图中的引用。
/// 注意:每次调用都会创建新的引擎实例,批量处理请使用 UpdateAll。
/// </summary>
public static void UpdateReferences(this object obj, IReferenceResolver resolver, object? userContext = null)
=> new ReferenceUpdateEngine(resolver, userContext: userContext).Update(obj);
/// <summary>
/// 使用指定的解析引擎更新对象图中的引用(复用引擎实例)。
/// </summary>
public static void UpdateReferences(this object obj, ReferenceUpdateEngine engine)
=> engine.Update(obj);
/// <summary>
/// 使用委托函数更新对象图中的引用。
/// 注意:每次调用都会创建新的引擎实例,批量处理请使用 UpdateAll。
/// </summary>
public static void UpdateReferences(this object obj, Func<object?, ReferenceTarget, ReferenceContext, object?> resolveFunc, object? userContext = null)
=> obj.UpdateReferences(new DelegateResolver(resolveFunc), userContext);
/// <summary>
/// 使用简单的字符串转换函数更新对象图中的引用。
/// 注意:每次调用都会创建新的引擎实例,批量处理请使用 UpdateAll。
/// </summary>
public static void UpdateReferencesSimple(this object obj, Func<string, string> resolve, object? userContext = null)
=> obj.UpdateReferences((old, _, ctx) => old is string s ? resolve(s) : old, userContext);
}
// ==================== 声明式引用标记系统 ====================
/// <summary>
/// 标记属性为引用字段,支持声明式配置引用解析行为。
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class ReferenceAttribute : Attribute
{
/// <summary> 引用目标类型 </summary>
public ReferenceTarget Target { get; set; } = ReferenceTarget.PrimaryKey;
/// <summary>
/// 是否为序列化内容(如 JSON 字符串)。
/// 若为 true,将使用 ContentHandlerType 处理内容。
/// </summary>
public bool IsSerializedContent { get; set; } = false;
/// <summary>
/// 内容处理器类型,需实现 ISerializedContentHandler 接口。
/// 仅当 IsSerializedContent 为 true 时生效。
/// </summary>
public Type? ContentHandlerType { get; set; }
/// <summary>
/// 内容处理器的构造函数参数(可选)。
/// 支持传递给 ContentHandlerType 构造函数的参数,用于需要参数的处理器。
/// 例如:JsonContentHandler 需要 jsonPath 参数。
/// </summary>
public object?[]? ContentHandlerArgs { get; set; }
/// <summary>
/// 是否递归解析解析后的值。
/// </summary>
public bool Recurse { get; set; } = true;
}
/// <summary>
/// JSON 数组内容处理器,解析 JSON 数组字符串中的引用。
/// </summary>
public class JsonArrayContentHandler : ISerializedContentHandler
{
public string Process(string content, ReferenceTarget target, ReferenceContext context, IReferenceResolver resolver)
{
if (string.IsNullOrEmpty(content)) return content;
var items = JsonConvert.DeserializeObject<List<string>>(content);
if (items == null || items.Count == 0) return content;
for (int i = 0; i < items.Count; i++)
{
var resolved = resolver.Resolve(items[i], target, context);
items[i] = resolved?.ToString() ?? items[i];
}
return JsonConvert.SerializeObject(items);
}
}
/// <summary>
/// JSON 对象内容处理器,递归解析 JSON 对象中的所有字符串值。
/// </summary>
public class JsonObjectContentHandler : ISerializedContentHandler
{
public string Process(string content, ReferenceTarget target, ReferenceContext context, IReferenceResolver resolver)
{
if (string.IsNullOrEmpty(content)) return content;
var token = JToken.Parse(content);
var newToken = JsonReferenceHelper.ReplaceReferences(token, target, context, resolver);
return newToken.ToString(Formatting.None);
}
}
// ==================== 声明式引用标记支持 ====================
/// <summary>
/// 使用 [Reference] 特性定义解析对象的引用。
/// </summary>
public static class ReferenceAttributeExtensions
{
/// <summary>
/// 使用 [Reference] 特性定义解析对象的引用。
/// </summary>
public static void ResolveAttributes(this object obj, IReferenceResolver resolver, object? userContext = null)
{
var provider = new AttributeReferenceDefinitionProvider();
var definitions = provider.GetDefinitions(obj.GetType());
if (definitions == null || definitions.Count == 0) return;
var type = obj.GetType();
var registry = new TypeReferenceRegistry();
foreach (var kvp in definitions)
{
var memberName = kvp.Key;
var def = kvp.Value;
registry.Register(type, new ReferenceDefinition(def.Target, def.IsSerializedContent, def.ContentHandler, def.Recurse)
{
Condition = accessor => accessor.Description.EndsWith($".{memberName}")
});
}
var engine = new ReferenceUpdateEngine(resolver, registry: registry, userContext: userContext);
engine.Update(obj);
}
}
/// <summary>
/// 基于特性的引用定义提供器,从 [Reference] 特性提取引用定义。
/// </summary>
public class AttributeReferenceDefinitionProvider
{
private readonly ConcurrentDictionary<Type, Dictionary<string, ReferenceDefinition>> _cache = new();
public IReadOnlyDictionary<string, ReferenceDefinition>? GetDefinitions(Type type)
{
if (_cache.TryGetValue(type, out var cached))
return cached;
var definitions = new Dictionary<string, ReferenceDefinition>();
var properties = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
var fields = type.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
foreach (var prop in properties)
{
var attr = prop.GetCustomAttributes(typeof(ReferenceAttribute), true).FirstOrDefault() as ReferenceAttribute;
if (attr != null)
{
definitions[prop.Name] = CreateDefinition(attr);
}
}
foreach (var field in fields)
{
var attr = field.GetCustomAttributes(typeof(ReferenceAttribute), true).FirstOrDefault() as ReferenceAttribute;
if (attr != null)
{
definitions[field.Name] = CreateDefinition(attr);
}
}
if (definitions.Count == 0)
return null;
_cache[type] = definitions;
return definitions;
}
private static ReferenceDefinition CreateDefinition(ReferenceAttribute attr)
{
ISerializedContentHandler? contentHandler = null;
if (attr.IsSerializedContent && attr.ContentHandlerType != null)
{
var handler = attr.ContentHandlerArgs != null && attr.ContentHandlerArgs.Length > 0
? Activator.CreateInstance(attr.ContentHandlerType, attr.ContentHandlerArgs)
: Activator.CreateInstance(attr.ContentHandlerType);
contentHandler = handler as ISerializedContentHandler;
if (handler != null && contentHandler == null)
{
throw new InvalidOperationException($"ContentHandlerType {attr.ContentHandlerType.Name} does not implement ISerializedContentHandler interface.");
}
}
return new ReferenceDefinition(attr.Target, attr.IsSerializedContent, contentHandler, attr.Recurse);
}
}
}
#region 声明式字段级引用映射机制
namespace ReferenceManagement.FieldMapping
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
/// <summary>
/// 字段引用映射配置
/// </summary>
public class FieldReferenceMapping
{
/// <summary>
/// 源属性路径(例如 "NextNodeKey" 或 "TransitionMap.NextKey")
/// 支持点分隔的多级路径
/// </summary>
public string SourcePropertyPath { get; set; } = string.Empty;
/// <summary>
/// 目标节点类型(用于精确匹配,可选)
/// </summary>
public Type? TargetNodeType { get; set; }
/// <summary>
/// 目标属性名(例如 "Callkey" 或 "Symbol")
/// 支持点分隔的多级路径
/// </summary>
public string TargetPropertyPath { get; set; } = string.Empty;
/// <summary>
/// 解析模式:如何从旧值定位目标节点并获取新值
/// </summary>
public FieldReferenceMode Mode { get; set; } = FieldReferenceMode.ByValueAsNodeIdentifier;
/// <summary>
/// 自定义解析委托(优先级最高)
/// 参数:旧值、源属性路径、当前节点对象、上下文
/// 返回:新值
/// </summary>
public Func<object?, string, object, object?, object?>? CustomResolver { get; set; }
}
/// <summary>
/// 字段引用解析模式
/// </summary>
public enum FieldReferenceMode
{
/// <summary>
/// 旧值即为目标节点的标识符(如 Callkey 或 Symbol),
/// 通过标识符查找节点,然后获取该节点的 TargetPropertyPath 属性值
/// </summary>
ByValueAsNodeIdentifier,
/// <summary>
/// 旧值本身就是目标节点上 TargetPropertyPath 的值(例如旧值就是目标 Callkey),
/// 直接通过重命名映射(renameMap)将旧值转换为新值
/// </summary>
ByValueDirectRename,
/// <summary>
/// 使用自定义解析委托
/// </summary>
Custom
}
/// <summary>
/// 字段级引用解析器,支持声明式映射配置
/// </summary>
public class FieldMappingReferenceResolver : IReferenceResolver
{
private const string PropertyPathSeparator = ".";
private readonly IReadOnlyList<FieldReferenceMapping> _mappings;
private readonly INodeLookup _nodeLookup;
private readonly IReadOnlyDictionary<string, string>? _renameMap;
private readonly IReadOnlyDictionary<string, string>? _symbolRenameMap;
private readonly ConcurrentDictionary<string, FieldReferenceMapping?> _mappingCache = new();
private readonly Dictionary<string, FieldReferenceMapping?> _exactMatchCache = new();
public interface INodeLookup
{
object? FindNodeByIdentifier(string identifier);
T? GetPropertyValue<T>(object node, string propertyPath);
}
public FieldMappingReferenceResolver(
IEnumerable<FieldReferenceMapping> mappings,
INodeLookup nodeLookup,
IReadOnlyDictionary<string, string>? renameMap = null,
IReadOnlyDictionary<string, string>? symbolRenameMap = null)
{
_mappings = mappings.ToList().AsReadOnly();
_nodeLookup = nodeLookup ?? throw new ArgumentNullException(nameof(nodeLookup));
_renameMap = renameMap ?? new Dictionary<string, string>();
_symbolRenameMap = symbolRenameMap ?? new Dictionary<string, string>();
PrebuildExactMatchCache();
}
private void PrebuildExactMatchCache()
{
foreach (var mapping in _mappings)
{
if (!string.IsNullOrEmpty(mapping.SourcePropertyPath))
_exactMatchCache[mapping.SourcePropertyPath] = mapping;
}
}
public object? Resolve(object? oldValue, ReferenceTarget target, ReferenceContext context)
{
if (oldValue == null) return null;
if (oldValue is not string strValue) return oldValue;
// 根据当前路径查找匹配的映射
var mapping = GetMappingForPath(context.Path);
if (mapping == null)
throw new InvalidOperationException($"No field reference mapping found for path '{context.Path}'.");
// 优先使用自定义解析器
if (mapping.Mode == FieldReferenceMode.Custom && mapping.CustomResolver != null)
{
return mapping.CustomResolver(oldValue, context.Path!, context.CurrentObject, context.UserContext);
}
// 根据模式解析
return mapping.Mode switch
{
FieldReferenceMode.ByValueAsNodeIdentifier => ResolveByIdentifier(strValue, mapping, context),
FieldReferenceMode.ByValueDirectRename => ResolveDirectRename(strValue, mapping),
_ => throw new NotSupportedException($"Unsupported field reference mode: {mapping.Mode}")
};
}
private FieldReferenceMapping? GetMappingForPath(string path)
{
if (string.IsNullOrEmpty(path)) return null;
if (_exactMatchCache.TryGetValue(path, out var exactMatch))
return exactMatch;
return _mappingCache.GetOrAdd(path, p =>
_mappings.FirstOrDefault(m => IsPathMatch(p, m.SourcePropertyPath))
);
}
private static bool IsPathMatch(string currentPath, string mappingPath)
{
if (string.Equals(currentPath, mappingPath, StringComparison.Ordinal))
return true;
if (currentPath.Length > mappingPath.Length + 1 &&
currentPath[currentPath.Length - mappingPath.Length - 1] == PropertyPathSeparator[0] &&
currentPath.EndsWith(mappingPath, StringComparison.Ordinal))
return true;
return false;
}
private object? ResolveByIdentifier(string identifier, FieldReferenceMapping mapping, ReferenceContext context)
{
var actualIdentifier = TransformIdentifier(identifier);
var targetNode = _nodeLookup.FindNodeByIdentifier(actualIdentifier);
if (targetNode == null)
throw new InvalidOperationException($"Target node not found for identifier '{actualIdentifier}'.");
var newValue = _nodeLookup.GetPropertyValue<object>(targetNode, mapping.TargetPropertyPath);
if (newValue == null)
throw new InvalidOperationException($"Property '{mapping.TargetPropertyPath}' returned null for node '{actualIdentifier}'.");
return newValue;
}
private string TransformIdentifier(string identifier)
{
string actualIdentifier = _renameMap!.GetValueOrDefault(identifier);
actualIdentifier = _symbolRenameMap!.GetValueOrDefault(actualIdentifier);
return actualIdentifier;
}
private object? ResolveDirectRename(string oldValue, FieldReferenceMapping mapping)
{
// 直接应用 renameMap 或 symbolRenameMap
if (_renameMap!.TryGetValue(oldValue, out var newCallkey))
return newCallkey;
if (_symbolRenameMap!.TryGetValue(oldValue, out var newSymbol))
return newSymbol;
throw new InvalidOperationException($"No rename mapping found for value '{oldValue}'.");
}
}
/// <summary>
/// 默认节点查找实现(适用于 NodeConfig 类型)
/// </summary>
public class NodeConfigLookup : FieldMappingReferenceResolver.INodeLookup
{
private const string PropertyPathSeparator = ".";
private readonly Func<string, object?> _findByIdentifier;
private readonly ConcurrentDictionary<string, string[]> _pathCache = new();
private readonly ConcurrentDictionary<(Type Type, string Property), PropertyInfo> _propertyCache = new();
public NodeConfigLookup(Func<string, object?> findByIdentifier)
{
_findByIdentifier = findByIdentifier ?? throw new ArgumentNullException(nameof(findByIdentifier));
}
public object? FindNodeByIdentifier(string identifier)
{
return _findByIdentifier(identifier);
}
public T? GetPropertyValue<T>(object node, string propertyPath)
{
var value = GetPropertyValue(node, propertyPath);
return value is T t ? t : default;
}
private object? GetPropertyValue(object node, string propertyPath)
{
if (string.IsNullOrEmpty(propertyPath))
throw new ArgumentException("Property path cannot be null or empty.", nameof(propertyPath));
var parts = _pathCache.GetOrAdd(propertyPath, p => p.Split(PropertyPathSeparator));
object current = node;
foreach (var part in parts)
{
if (current == null)
throw new InvalidOperationException($"Cannot navigate property path '{propertyPath}': intermediate object is null at '{part}'.");
var cacheKey = (current.GetType(), part);
var prop = _propertyCache.GetOrAdd(cacheKey, k =>
k.Type.GetProperty(k.Property, BindingFlags.Public | BindingFlags.Instance));
if (prop == null)
throw new InvalidOperationException($"Property '{part}' not found on type '{current.GetType().FullName}' in path '{propertyPath}'.");
current = prop.GetValue(current);
}
return current;
}
}
/// <summary>
/// 便捷的构建器,用于创建 FieldMappingReferenceResolver
/// </summary>
public class FieldMappingResolverBuilder
{
private readonly List<FieldReferenceMapping> _mappings = new();
private FieldMappingReferenceResolver.INodeLookup? _nodeLookup;
private IReadOnlyDictionary<string, string>? _renameMap;
private IReadOnlyDictionary<string, string>? _symbolRenameMap;
public FieldMappingResolverBuilder AddMapping(string sourcePropertyPath, string targetPropertyPath,
FieldReferenceMode mode = FieldReferenceMode.ByValueAsNodeIdentifier)
{
_mappings.Add(new FieldReferenceMapping
{
SourcePropertyPath = sourcePropertyPath,
TargetPropertyPath = targetPropertyPath,
Mode = mode
});
return this;
}
public FieldMappingResolverBuilder AddCustomMapping(string sourcePropertyPath,
Func<object?, string, object, object?, object?> customResolver)
{
_mappings.Add(new FieldReferenceMapping
{
SourcePropertyPath = sourcePropertyPath,
Mode = FieldReferenceMode.Custom,
CustomResolver = customResolver
});
return this;
}
public FieldMappingResolverBuilder WithNodeLookup(FieldMappingReferenceResolver.INodeLookup nodeLookup)
{
_nodeLookup = nodeLookup;
return this;
}
public FieldMappingResolverBuilder WithRenameMaps(
IReadOnlyDictionary<string, string>? renameMap,
IReadOnlyDictionary<string, string>? symbolRenameMap = null)
{
_renameMap = renameMap;
_symbolRenameMap = symbolRenameMap;
return this;
}
public FieldMappingReferenceResolver Build()
{
if (_nodeLookup == null)
throw new InvalidOperationException("NodeLookup is required.");
return new FieldMappingReferenceResolver(_mappings, _nodeLookup, _renameMap, _symbolRenameMap);
}
}
/// <summary>
/// 扩展方法,方便在去重流程中集成字段级引用映射
/// </summary>
public static class FieldMappingExtensions
{
public static ReferenceUpdateEngine CreateEngineWithFieldMappings(
IEnumerable<FieldReferenceMapping> mappings,
FieldMappingReferenceResolver.INodeLookup nodeLookup,
IReadOnlyDictionary<string, string>? renameMap = null,
IReadOnlyDictionary<string, string>? symbolRenameMap = null,
object? userContext = null)
{
var resolver = new FieldMappingReferenceResolver(mappings, nodeLookup, renameMap, symbolRenameMap);
return new ReferenceUpdateEngine(resolver, userContext: userContext);
}
}
}
#endregion
#endregion