using System.Buffers; using System.Collections.Concurrent; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Text; using AyCode.Core.Interfaces; using AyCode.Core.Serializers; using AyCode.Core.Serializers.Binaries; using AyCode.Core.Serializers.Jsons; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using static AyCode.Core.Helpers.JsonUtilities; using static AyCode.Core.Serializers.Binaries.AcBinaryDeserializer; namespace AyCode.Core.Extensions; /// /// High-performance Base62 encoder for compact $id/$ref values. /// internal static class Base62 { private const string Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string Encode(long value) { if (value == 0) return "0"; var isNegative = value < 0; if (isNegative) value = -value; Span buffer = stackalloc char[16]; var index = buffer.Length; while (value > 0) { buffer[--index] = Alphabet[(int)(value % 62)]; value /= 62; } if (isNegative) buffer[--index] = '-'; return new string(buffer[index..]); } } /// /// High-performance hybrid reference resolver using Base62 encoded semantic IDs. /// public class HybridReferenceResolver : IReferenceResolver { internal Dictionary? _idToObject; internal Dictionary? _objectToId; internal HashSet? _referencedIds; private int _nextNumericId = 1; private static readonly ConcurrentDictionary> IdGetterCache = new(); public bool IsForMerge { get; } private readonly int _estimatedObjectCount; public HybridReferenceResolver(bool isForMerge = false, int estimatedObjectCount = 64) { IsForMerge = isForMerge; _estimatedObjectCount = estimatedObjectCount; } internal HashSet ReferencedIds => _referencedIds ??= new HashSet(_estimatedObjectCount / 4, StringComparer.Ordinal); [MethodImpl(MethodImplOptions.AggressiveInlining)] private Dictionary GetIdToObject() => _idToObject ??= new Dictionary(_estimatedObjectCount, StringComparer.Ordinal); [MethodImpl(MethodImplOptions.AggressiveInlining)] private Dictionary GetObjectToId() => _objectToId ??= new Dictionary(_estimatedObjectCount, AyCode.Core.Serializers.ReferenceEqualityComparer.Instance); public void AddReference(object context, string reference, object value) { GetIdToObject()[reference] = value; GetObjectToId()[value] = reference; } public string GetReference(object context, object value) { var objectToId = GetObjectToId(); if (objectToId.TryGetValue(value, out var existingId)) { if (!IsForMerge) ReferencedIds.Add(existingId); return existingId; } var type = value.GetType(); var (isId, idType) = GetIdInfo(type); string newRef; if (isId && idType != null) { var idGetter = GetOrCreateIdGetter(type); var idValue = idGetter(value); if (idValue != null && !IsDefaultValue(idValue, idType)) { var typeId = TypeCache.GetTypeId(type); var objectIdAsLong = TypeCache.IdToLong(idValue); var semanticId = TypeCache.CreateSemanticId(typeId, objectIdAsLong); newRef = Base62.Encode(semanticId); } else newRef = Base62.Encode(-_nextNumericId++); } else newRef = Base62.Encode(-_nextNumericId++); GetIdToObject()[newRef] = value; objectToId[value] = newRef; return newRef; } public bool IsReferenced(object context, object value) => _objectToId?.ContainsKey(value) ?? false; public object ResolveReference(object context, string reference) => _idToObject != null && _idToObject.TryGetValue(reference, out var value) ? value : null!; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Func GetOrCreateIdGetter(Type type) => IdGetterCache.GetOrAdd(type, static t => { var prop = t.GetProperty("Id"); if (prop == null) return static _ => null; var getMethod = prop.GetGetMethod(); if (getMethod == null) return static _ => null; return obj => getMethod.Invoke(obj, null); }); } internal static class JsonReferencePostProcessor { private const string IdMarker = "\"$id\""; public static string RemoveUnreferencedIds(string json, HashSet? referencedIds) { if (!json.Contains(IdMarker)) return json; return referencedIds == null || referencedIds.Count == 0 ? RemoveAllIdsSpan(json) : RemoveUnreferencedIdsSpan(json, referencedIds); } private static string RemoveAllIdsSpan(string json) { var sb = new StringBuilder(json.Length); var lastCopyEnd = 0; var searchStart = 0; while (searchStart < json.Length) { var idIndex = json.IndexOf(IdMarker, searchStart, StringComparison.Ordinal); if (idIndex < 0) break; if (idIndex > lastCopyEnd) sb.Append(json, lastCopyEnd, idIndex - lastCopyEnd); var endIndex = SkipIdEntry(json, idIndex); lastCopyEnd = endIndex; searchStart = endIndex; } if (lastCopyEnd < json.Length) sb.Append(json, lastCopyEnd, json.Length - lastCopyEnd); return sb.Length == json.Length ? json : sb.ToString(); } private static string RemoveUnreferencedIdsSpan(string json, HashSet referencedIds) { var sb = new StringBuilder(json.Length); var lastCopyEnd = 0; var searchStart = 0; while (searchStart < json.Length) { var idIndex = json.IndexOf(IdMarker, searchStart, StringComparison.Ordinal); if (idIndex < 0) break; var valueStart = idIndex + IdMarker.Length; while (valueStart < json.Length && (json[valueStart] == ' ' || json[valueStart] == ':')) valueStart++; string? idValue = null; var valueEnd = valueStart; if (valueStart < json.Length && json[valueStart] == '"') { valueStart++; valueEnd = valueStart; while (valueEnd < json.Length && json[valueEnd] != '"') valueEnd++; idValue = json.Substring(valueStart, valueEnd - valueStart); valueEnd++; } while (valueEnd < json.Length && (json[valueEnd] == ' ' || json[valueEnd] == ',')) valueEnd++; if (idValue != null && referencedIds.Contains(idValue)) searchStart = valueEnd; else { if (idIndex > lastCopyEnd) sb.Append(json, lastCopyEnd, idIndex - lastCopyEnd); lastCopyEnd = valueEnd; searchStart = valueEnd; } } if (lastCopyEnd == 0) return json; if (lastCopyEnd < json.Length) sb.Append(json, lastCopyEnd, json.Length - lastCopyEnd); return sb.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int SkipIdEntry(string json, int idIndex) { var pos = idIndex + IdMarker.Length; while (pos < json.Length && (json[pos] == ' ' || json[pos] == ':')) pos++; if (pos < json.Length && json[pos] == '"') { pos++; while (pos < json.Length && json[pos] != '"') pos++; if (pos < json.Length) pos++; } while (pos < json.Length && (json[pos] == ' ' || json[pos] == ',')) pos++; return pos; } public static HashSet CollectReferencedIds(string json) { const string refMarker = "\"$ref\""; var result = new HashSet(StringComparer.Ordinal); var searchStart = 0; while (searchStart < json.Length) { var refIndex = json.IndexOf(refMarker, searchStart, StringComparison.Ordinal); if (refIndex < 0) break; var valueStart = refIndex + refMarker.Length; while (valueStart < json.Length && (json[valueStart] == ' ' || json[valueStart] == ':')) valueStart++; if (valueStart < json.Length && json[valueStart] == '"') { valueStart++; var valueEnd = valueStart; while (valueEnd < json.Length && json[valueEnd] != '"') valueEnd++; if (valueEnd > valueStart) result.Add(json.Substring(valueStart, valueEnd - valueStart)); searchStart = valueEnd + 1; } else searchStart = valueStart; } return result; } } internal sealed class PooledStringWriter : StringWriter { private static readonly ObjectPool StringBuilderPool = new DefaultObjectPool(new StringBuilderPooledObjectPolicy { InitialCapacity = 4096, MaximumRetainedCapacity = 4 * 1024 * 1024 }); private readonly StringBuilder _pooledBuilder; private bool _disposed; private PooledStringWriter(StringBuilder sb) : base(sb) => _pooledBuilder = sb; public static PooledStringWriter Rent() { var sb = StringBuilderPool.Get(); sb.Clear(); return new PooledStringWriter(sb); } protected override void Dispose(bool disposing) { if (!_disposed) { _disposed = true; StringBuilderPool.Return(_pooledBuilder); } base.Dispose(disposing); } } internal interface ObjectPool where T : class { T Get(); void Return(T obj); } internal sealed class DefaultObjectPool : ObjectPool where T : class { [ThreadStatic] private static T? _threadLocalItem; private readonly ConcurrentQueue _pool = new(); private readonly IPooledObjectPolicy _policy; private const int MaxPoolSize = 8; public DefaultObjectPool(IPooledObjectPolicy policy) => _policy = policy; public T Get() { var item = _threadLocalItem; if (item != null) { _threadLocalItem = null; return item; } return _pool.TryDequeue(out item) ? item : _policy.Create(); } public void Return(T obj) { if (!_policy.Return(obj)) return; if (_threadLocalItem == null) { _threadLocalItem = obj; return; } if (_pool.Count < MaxPoolSize) _pool.Enqueue(obj); } } internal interface IPooledObjectPolicy { T Create(); bool Return(T obj); } internal sealed class StringBuilderPooledObjectPolicy : IPooledObjectPolicy { public int InitialCapacity { get; init; } = 256; public int MaximumRetainedCapacity { get; init; } = 4 * 1024 * 1024; public StringBuilder Create() => new(InitialCapacity); public bool Return(StringBuilder obj) { if (obj.Capacity > MaximumRetainedCapacity) return false; obj.Clear(); return true; } } public static class SerializeObjectExtensions { private static readonly UnifiedMergeContractResolver SharedContractResolver = new(); private static readonly Dictionary EmptyContextDict = new(); public static JsonSerializerSettings Options => new() { ContractResolver = SharedContractResolver, Context = new StreamingContext(StreamingContextStates.All, EmptyContextDict), PreserveReferencesHandling = PreserveReferencesHandling.Objects, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, ReferenceResolverProvider = () => new HybridReferenceResolver(), NullValueHandling = NullValueHandling.Ignore, MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead, Formatting = Formatting.None, }; #region JSON Serialization /// /// Serialize object to JSON string with default options. /// public static string ToJson(this T source) => AcJsonSerializer.Serialize(source); /// /// Serialize object to JSON string with specified options. /// public static string ToJson(this T source, AcJsonSerializerOptions options) => AcJsonSerializer.Serialize(source, options); public static string ToJson(this IQueryable source) where T : class, IAcSerializableToJson => AcJsonSerializer.Serialize(source); public static string ToJson(this IQueryable source, AcJsonSerializerOptions options) where T : class, IAcSerializableToJson => AcJsonSerializer.Serialize(source, options); public static string ToJson(this IEnumerable source) where T : class, IAcSerializableToJson => AcJsonSerializer.Serialize(source); public static string ToJson(this IEnumerable source, AcJsonSerializerOptions options) where T : class, IAcSerializableToJson => AcJsonSerializer.Serialize(source, options); /// /// Deserialize JSON to object with default options. /// public static T? JsonTo(this string json) { json = UnwrapJsonString(json); return AcJsonDeserializer.Deserialize(json); } /// /// Deserialize JSON to object with specified options. /// public static T? JsonTo(this string json, AcJsonSerializerOptions options) { json = UnwrapJsonString(json); return AcJsonDeserializer.Deserialize(json, options); } /// /// Deserialize UTF-8 encoded JSON bytes to object with default options. /// Zero-allocation path - no string conversion needed. /// public static T? JsonTo(this ReadOnlySpan utf8Json) => AcJsonDeserializer.Deserialize(utf8Json); /// /// Deserialize UTF-8 encoded JSON bytes to object with specified options. /// Zero-allocation path - no string conversion needed. /// public static T? JsonTo(this ReadOnlySpan utf8Json, AcJsonSerializerOptions options) => AcJsonDeserializer.Deserialize(utf8Json, options); /// /// Deserialize UTF-8 encoded JSON bytes to object with default options. /// Zero-allocation path - no string conversion needed. /// public static T? JsonTo(this byte[] utf8Json) => AcJsonDeserializer.Deserialize(utf8Json.AsSpan()); /// /// Deserialize UTF-8 encoded JSON bytes to object with specified options. /// Zero-allocation path - no string conversion needed. /// public static T? JsonTo(this byte[] utf8Json, AcJsonSerializerOptions options) => AcJsonDeserializer.Deserialize(utf8Json.AsSpan(), options); /// /// Deserialize JSON to specified type with default options. /// public static object? JsonTo(this string json, Type toType) { json = UnwrapJsonString(json); return AcJsonDeserializer.Deserialize(json, toType); } /// /// Deserialize JSON to specified type with specified options. /// public static object? JsonTo(this string json, Type toType, AcJsonSerializerOptions options) { json = UnwrapJsonString(json); return AcJsonDeserializer.Deserialize(json, toType, options); } /// /// Populate existing object from JSON with default options. /// public static void JsonTo(this string json, object target) { json = UnwrapJsonString(json); AcJsonDeserializer.Populate(json, target); } /// /// Populate existing object from JSON with specified options. /// public static void JsonTo(this string json, object target, AcJsonSerializerOptions options) { json = UnwrapJsonString(json); AcJsonDeserializer.Populate(json, target, options); } /// /// Create a deserialize chain that parses JSON once and allows multiple deserializations. /// Efficient for deserializing the same JSON to multiple different types. /// Use with 'using' statement or call Dispose() when done. /// public static IDeserializeChain JsonToChain(this string json) { json = UnwrapJsonString(json); return AcJsonDeserializer.CreateDeserializeChain(json); } /// /// Create a deserialize chain with options. /// public static IDeserializeChain JsonToChain(this string json, AcJsonSerializerOptions options) { json = UnwrapJsonString(json); return AcJsonDeserializer.CreateDeserializeChain(json, options); } /// /// Create a populate chain that parses JSON once and allows populating multiple objects. /// Efficient for populating multiple objects from the same JSON source. /// Use with 'using' statement or call Dispose() when done. /// public static IDeserializeChain JsonToChain(this string json, object target) { json = UnwrapJsonString(json); var chain = AcJsonDeserializer.CreateDeserializeChain(json); chain.ThenPopulate(target); return chain; } /// /// Create a populate chain with options. /// public static IDeserializeChain JsonToChain(this string json, object target, AcJsonSerializerOptions options) { json = UnwrapJsonString(json); var chain = AcJsonDeserializer.CreateDeserializeChain(json, options); chain.ThenPopulate(target); return chain; } #endregion #region Any (JSON or Binary based on options) public static object ToAny(this T source, AcSerializerOptions options) { if (options.SerializerType == AcSerializerType.Json) return ToJson(source, (AcJsonSerializerOptions)options); return ToBinary(source, (AcBinarySerializerOptions)options); } /// /// Deserialize data (JSON string or binary byte[]) to object based on options. /// public static T? AnyTo(this object data, AcSerializerOptions options) { if (options.SerializerType == AcSerializerType.Json) return ((string)data).JsonTo((AcJsonSerializerOptions)options); return ((byte[])data).BinaryTo(); } /// /// Deserialize data to specified type based on options. /// public static object? AnyTo(this object data, Type targetType, AcSerializerOptions options) { if (options.SerializerType == AcSerializerType.Json) return ((string)data).JsonTo(targetType, (AcJsonSerializerOptions)options); return ((byte[])data).BinaryTo(targetType); } /// /// Populate existing object from data based on options. /// public static void AnyTo(this object data, T target, AcSerializerOptions options) where T : class { if (options.SerializerType == AcSerializerType.Json) ((string)data).JsonTo(target, (AcJsonSerializerOptions)options); else ((byte[])data).BinaryTo(target); } /// /// Populate existing object with merge semantics based on options. /// public static void AnyToMerge(this object data, T target, AcSerializerOptions options) where T : class { if (options.SerializerType == AcSerializerType.Json) ((string)data).JsonTo(target, (AcJsonSerializerOptions)options); else ((byte[])data).BinaryToMerge(target); } #endregion #region Binary Serialization /// /// Serialize object to binary byte array with default options. /// public static byte[] ToBinary(this T source) => AcBinarySerializer.Serialize(source); /// /// Serialize object to binary byte array with specified options. /// public static byte[] ToBinary(this T source, AcBinarySerializerOptions options) => AcBinarySerializer.Serialize(source, options); /// /// Serialize object directly to an IBufferWriter for zero-copy scenarios. /// public static void ToBinary(this T source, IBufferWriter writer) => AcBinarySerializer.Serialize(source, writer, AcBinarySerializerOptions.Default); /// /// Serialize object directly to an IBufferWriter with specified options. /// public static void ToBinary(this T source, IBufferWriter writer, AcBinarySerializerOptions options) => AcBinarySerializer.Serialize(source, writer, options); /// /// Get the serialized binary size without allocating the final array. /// public static int GetBinarySize(this T source) => AcBinarySerializer.GetSerializedSize(source, AcBinarySerializerOptions.Default); /// /// Get the serialized binary size with specified options. /// public static int GetBinarySize(this T source, AcBinarySerializerOptions options) => AcBinarySerializer.GetSerializedSize(source, options); /// /// Deserialize binary data to object. /// public static T? BinaryTo(this byte[] data) => AcBinaryDeserializer.Deserialize(data); /// /// Deserialize binary data from ReadOnlySpan. /// public static T? BinaryTo(this ReadOnlySpan data) => AcBinaryDeserializer.Deserialize(data); /// /// Deserialize binary data from ReadOnlyMemory. /// public static T? BinaryTo(this ReadOnlyMemory data) => AcBinaryDeserializer.Deserialize(data.Span); /// /// Deserialize binary data to specified type. /// public static object? BinaryTo(this byte[] data, Type targetType) => AcBinaryDeserializer.Deserialize(data.AsSpan(), targetType); /// /// Deserialize binary data from ReadOnlySpan to specified type. /// public static object? BinaryTo(this ReadOnlySpan data, Type targetType) => AcBinaryDeserializer.Deserialize(data, targetType); /// /// Deserialize binary data from ReadOnlyMemory to specified type. /// public static object? BinaryTo(this ReadOnlyMemory data, Type targetType) => AcBinaryDeserializer.Deserialize(data.Span, targetType); /// /// Populate existing object from binary data. /// public static void BinaryTo(this byte[] data, T target) where T : class => AcBinaryDeserializer.Populate(data, target); /// /// Populate existing object from binary ReadOnlySpan. /// public static void BinaryTo(this ReadOnlySpan data, T target) where T : class => AcBinaryDeserializer.Populate(data, target); /// /// Populate existing object from binary ReadOnlyMemory. /// public static void BinaryTo(this ReadOnlyMemory data, T target) where T : class => AcBinaryDeserializer.Populate(data.Span, target); /// /// Populate existing object from binary data with merge semantics for IId collections. /// public static void BinaryToMerge(this byte[] data, T target) where T : class => AcBinaryDeserializer.PopulateMerge(data.AsSpan(), target); /// /// Populate existing object from binary ReadOnlySpan with merge semantics. /// public static void BinaryToMerge(this ReadOnlySpan data, T target) where T : class => AcBinaryDeserializer.PopulateMerge(data, target); /// /// Populate existing object from binary ReadOnlyMemory with merge semantics. /// public static void BinaryToMerge(this ReadOnlyMemory data, T target) where T : class => AcBinaryDeserializer.PopulateMerge(data.Span, target); /// /// Create a deserialize chain that parses binary data once and allows multiple deserializations. /// Efficient for deserializing the same binary to multiple different types. /// Use with 'using' statement or call Dispose() when done. /// public static IDeserializeChain BinaryToChain(this byte[] data) => AcBinaryDeserializer.CreateDeserializeChain(data.AsSpan()); /// /// Create a deserialize chain with options. /// public static IDeserializeChain BinaryToChain(this byte[] data, AcBinarySerializerOptions options) => AcBinaryDeserializer.CreateDeserializeChain(data.AsSpan(), options); /// /// Create a deserialize chain from ReadOnlyMemory. /// public static IDeserializeChain BinaryToChain(this ReadOnlyMemory data) => AcBinaryDeserializer.CreateDeserializeChain(data.Span); /// /// Create a deserialize chain from ReadOnlyMemory with options. /// public static IDeserializeChain BinaryToChain(this ReadOnlyMemory data, AcBinarySerializerOptions options) => AcBinaryDeserializer.CreateDeserializeChain(data.Span, options); /// /// Create a populate chain that parses binary data once and allows populating multiple objects. /// Efficient for populating multiple objects from the same binary source. /// Use with 'using' statement or call Dispose() when done. /// public static IDeserializeChain BinaryToChain(this byte[] data, T target) where T : class { var chain = AcBinaryDeserializer.CreateDeserializeChain(data.AsSpan()); chain.ThenPopulate(target); return chain; } /// /// Create a populate chain with options. /// public static IDeserializeChain BinaryToChain(this byte[] data, T target, AcBinarySerializerOptions options) where T : class { var chain = AcBinaryDeserializer.CreateDeserializeChain(data.AsSpan(), options); chain.ThenPopulate(target); return chain; } /// /// Create a populate chain from ReadOnlyMemory. /// public static IDeserializeChain BinaryToChain(this ReadOnlyMemory data, T target) where T : class { var chain = AcBinaryDeserializer.CreateDeserializeChain(data.Span); chain.ThenPopulate(target); return chain; } /// /// Create a populate chain from ReadOnlyMemory with options. /// public static IDeserializeChain BinaryToChain(this ReadOnlyMemory data, T target, AcBinarySerializerOptions options) where T : class { var chain = AcBinaryDeserializer.CreateDeserializeChain(data.Span, options); chain.ThenPopulate(target); return chain; } #endregion #region Clone and Copy (Binary-based, zero intermediate allocation) /// /// Clone object via binary serialization (zero intermediate byte[] allocation). /// Uses ArrayBufferWriter to serialize directly into a buffer, then deserializes from the span. /// public static TDestination? CloneTo(this object? src) where TDestination : class { if (src == null) return null; var buffer = new ArrayBufferWriter(256); AcBinarySerializer.Serialize(src, buffer, AcBinarySerializerOptions.Default); return AcBinaryDeserializer.Deserialize(buffer.WrittenSpan); } /// /// Copy object properties to target via binary serialization (zero intermediate byte[] allocation). /// Uses ArrayBufferWriter to serialize directly into a buffer, then populates target from the span. /// public static void CopyTo(this object? src, object target) { if (src == null) return; var buffer = new ArrayBufferWriter(256); AcBinarySerializer.Serialize(src, buffer, AcBinarySerializerOptions.Default); AcBinaryDeserializer.Populate(buffer.WrittenSpan, target); } #endregion } public class IgnoreAndRenamePropertySerializerContractResolver : DefaultContractResolver { private readonly Dictionary> _ignores = new(); private readonly Dictionary> _includes = new(); private readonly Dictionary> _renames = new(); public void IgnoreProperty(Type type, params string[] jsonPropertyNames) { if (!_ignores.TryGetValue(type, out var set)) { set = new HashSet(StringComparer.Ordinal); _ignores[type] = set; } foreach (var prop in jsonPropertyNames) set.Add(prop); } public void IncludesProperty(Type type, params string[] jsonPropertyNames) { if (!_includes.TryGetValue(type, out var set)) { set = new HashSet(StringComparer.Ordinal); _includes[type] = set; } foreach (var prop in jsonPropertyNames) set.Add(prop); } public void RenameProperty(Type type, string propertyName, string newJsonPropertyName) { if (!_renames.TryGetValue(type, out var dict)) { dict = new Dictionary(StringComparer.Ordinal); _renames[type] = dict; } dict[propertyName] = newJsonPropertyName; } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); if (IsIgnored(property.DeclaringType, property.PropertyName) || !IsIncluded(property.DeclaringType, property.PropertyName)) { property.ShouldSerialize = _ => false; property.Ignored = true; } if (IsRenamed(property.DeclaringType, property.PropertyName, out var newName)) property.PropertyName = newName; return property; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool IsIgnored(Type? type, string? name) => type != null && name != null && _ignores.TryGetValue(type, out var set) && set.Contains(name); [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool IsIncluded(Type? type, string? name) => _includes.Count == 0 || (type != null && name != null && _includes.TryGetValue(type, out var set) && set.Contains(name)); [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool IsRenamed(Type? type, string? name, out string? newName) { if (type != null && name != null && _renames.TryGetValue(type, out var renames) && renames.TryGetValue(name, out newName)) return true; newName = null; return false; } }