diff --git a/GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs b/GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs
index 65919b25..fff89667 100644
--- a/GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs
+++ b/GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs
@@ -96,6 +96,7 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
var interfaceName = iContextAware.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat);
+ var memberNames = CreateGeneratedContextMemberNames(symbol);
sb.AppendLine("/// ");
sb.AppendLine("/// 为当前规则类型补充自动生成的架构上下文访问实现。");
sb.AppendLine("/// ");
@@ -107,15 +108,15 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine(
"/// 已缓存的实例上下文需要通过 显式覆盖。");
sb.AppendLine(
- "/// 与手动继承 的路径相比,生成实现会使用 _gFrameworkContextAwareSync 协调惰性初始化、provider 切换和显式上下文注入;");
+ $"/// 与手动继承 的路径相比,生成实现会使用 {memberNames.SyncFieldName} 协调惰性初始化、provider 切换和显式上下文注入;");
sb.AppendLine(
"/// 则保持无锁的实例级缓存语义,更适合已经由调用方线程模型保证串行访问的简单场景。");
sb.AppendLine("/// ");
sb.AppendLine($"partial class {symbol.Name} : {interfaceName}");
sb.AppendLine("{");
- GenerateContextProperty(sb);
- GenerateInterfaceImplementations(sb, iContextAware);
+ GenerateContextProperty(sb, memberNames);
+ GenerateInterfaceImplementations(sb, iContextAware, memberNames);
sb.AppendLine("}");
return sb.ToString().TrimEnd();
@@ -138,24 +139,29 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
/// 生成Context属性
///
/// 字符串构建器
- private static void GenerateContextProperty(StringBuilder sb)
+ /// 当前目标类型应使用的上下文字段名。
+ private static void GenerateContextProperty(
+ StringBuilder sb,
+ GeneratedContextMemberNames memberNames)
{
- GenerateContextBackingFields(sb);
- GenerateContextGetter(sb);
- GenerateContextProviderConfiguration(sb);
+ GenerateContextBackingFields(sb, memberNames);
+ GenerateContextGetter(sb, memberNames);
+ GenerateContextProviderConfiguration(sb, memberNames);
}
///
/// 生成上下文缓存和同步所需的字段。
///
/// 字符串构建器。
- private static void GenerateContextBackingFields(StringBuilder sb)
+ private static void GenerateContextBackingFields(
+ StringBuilder sb,
+ GeneratedContextMemberNames memberNames)
{
sb.AppendLine(
- " private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _gFrameworkContextAwareContext;");
+ $" private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? {memberNames.ContextFieldName};");
sb.AppendLine(
- " private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _gFrameworkContextAwareProvider;");
- sb.AppendLine(" private static readonly object _gFrameworkContextAwareSync = new();");
+ $" private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? {memberNames.ProviderFieldName};");
+ sb.AppendLine($" private static readonly object {memberNames.SyncFieldName} = new();");
sb.AppendLine();
}
@@ -163,7 +169,9 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
/// 生成实例上下文访问器,包含显式注入优先和 provider 惰性回退语义。
///
/// 字符串构建器。
- private static void GenerateContextGetter(StringBuilder sb)
+ private static void GenerateContextGetter(
+ StringBuilder sb,
+ GeneratedContextMemberNames memberNames)
{
sb.AppendLine(" /// ");
sb.AppendLine(" /// 获取当前实例绑定的架构上下文。");
@@ -178,7 +186,7 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine(
" /// 或 不会自动清除此缓存;如需覆盖,请显式调用 IContextAware.SetContext(...)。");
sb.AppendLine(
- " /// 当前实现还假设 可在持有 _gFrameworkContextAwareSync 时安全执行;");
+ $" /// 当前实现还假设 可在持有 {memberNames.SyncFieldName} 时安全执行;");
sb.AppendLine(
" /// 自定义 provider 不应在该调用链内重新进入当前类型的 provider 配置 API,且应避免引入与外部全局锁相互等待的锁顺序。");
sb.AppendLine(" /// ");
@@ -186,21 +194,15 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine(" {");
sb.AppendLine(" get");
sb.AppendLine(" {");
- sb.AppendLine(" var context = _gFrameworkContextAwareContext;");
- sb.AppendLine(" if (context is not null)");
- sb.AppendLine(" {");
- sb.AppendLine(" return context;");
- sb.AppendLine(" }");
- sb.AppendLine();
sb.AppendLine(" // 在同一个同步域内协调懒加载与 provider 切换,避免读取到被并发重置的空提供者。");
sb.AppendLine(
- " // provider 的 GetContext() 会在持有 _gFrameworkContextAwareSync 时执行;自定义 provider 必须避免在该调用链内回调 SetContextProvider/ResetContextProvider 或形成反向锁顺序。");
- sb.AppendLine(" lock (_gFrameworkContextAwareSync)");
+ $" // provider 的 GetContext() 会在持有 {memberNames.SyncFieldName} 时执行;自定义 provider 必须避免在该调用链内回调 SetContextProvider/ResetContextProvider 或形成反向锁顺序。");
+ sb.AppendLine($" lock ({memberNames.SyncFieldName})");
sb.AppendLine(" {");
sb.AppendLine(
- " _gFrameworkContextAwareProvider ??= new global::GFramework.Core.Architectures.GameContextProvider();");
- sb.AppendLine(" _gFrameworkContextAwareContext ??= _gFrameworkContextAwareProvider.GetContext();");
- sb.AppendLine(" return _gFrameworkContextAwareContext;");
+ $" {memberNames.ProviderFieldName} ??= new global::GFramework.Core.Architectures.GameContextProvider();");
+ sb.AppendLine($" {memberNames.ContextFieldName} ??= {memberNames.ProviderFieldName}.GetContext();");
+ sb.AppendLine($" return {memberNames.ContextFieldName};");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine(" }");
@@ -211,7 +213,9 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
/// 生成静态 provider 配置 API,供测试和宿主在懒加载前替换默认上下文来源。
///
/// 字符串构建器。
- private static void GenerateContextProviderConfiguration(StringBuilder sb)
+ private static void GenerateContextProviderConfiguration(
+ StringBuilder sb,
+ GeneratedContextMemberNames memberNames)
{
sb.AppendLine(" /// ");
sb.AppendLine(" /// 配置当前生成类型共享的上下文提供者。");
@@ -229,9 +233,9 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
" public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider)");
sb.AppendLine(" {");
sb.AppendLine(" global::System.ArgumentNullException.ThrowIfNull(provider);");
- sb.AppendLine(" lock (_gFrameworkContextAwareSync)");
+ sb.AppendLine($" lock ({memberNames.SyncFieldName})");
sb.AppendLine(" {");
- sb.AppendLine(" _gFrameworkContextAwareProvider = provider;");
+ sb.AppendLine($" {memberNames.ProviderFieldName} = provider;");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
@@ -246,9 +250,9 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine(" /// ");
sb.AppendLine(" public static void ResetContextProvider()");
sb.AppendLine(" {");
- sb.AppendLine(" lock (_gFrameworkContextAwareSync)");
+ sb.AppendLine($" lock ({memberNames.SyncFieldName})");
sb.AppendLine(" {");
- sb.AppendLine(" _gFrameworkContextAwareProvider = null;");
+ sb.AppendLine($" {memberNames.ProviderFieldName} = null;");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
@@ -265,7 +269,8 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
/// 接口符号
private static void GenerateInterfaceImplementations(
StringBuilder sb,
- INamedTypeSymbol interfaceSymbol)
+ INamedTypeSymbol interfaceSymbol,
+ GeneratedContextMemberNames memberNames)
{
var interfaceName = interfaceSymbol.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat);
@@ -275,7 +280,7 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
if (method.MethodKind != MethodKind.Ordinary)
continue;
- GenerateMethod(sb, interfaceName, method);
+ GenerateMethod(sb, interfaceName, method, memberNames);
sb.AppendLine();
}
}
@@ -289,7 +294,8 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
private static void GenerateMethod(
StringBuilder sb,
string interfaceName,
- IMethodSymbol method)
+ IMethodSymbol method,
+ GeneratedContextMemberNames memberNames)
{
var returnType = method.ReturnType.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat);
@@ -302,7 +308,7 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
$" {returnType} {interfaceName}.{method.Name}({parameters})");
sb.AppendLine(" {");
- GenerateMethodBody(sb, method);
+ GenerateMethodBody(sb, method, memberNames);
sb.AppendLine(" }");
}
@@ -314,15 +320,16 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
/// 方法符号
private static void GenerateMethodBody(
StringBuilder sb,
- IMethodSymbol method)
+ IMethodSymbol method,
+ GeneratedContextMemberNames memberNames)
{
switch (method.Name)
{
case "SetContext":
sb.AppendLine(" // 与 Context getter 共享同一同步协议,避免显式注入被并发懒加载覆盖。");
- sb.AppendLine(" lock (_gFrameworkContextAwareSync)");
+ sb.AppendLine($" lock ({memberNames.SyncFieldName})");
sb.AppendLine(" {");
- sb.AppendLine(" _gFrameworkContextAwareContext = context;");
+ sb.AppendLine($" {memberNames.ContextFieldName} = context;");
sb.AppendLine(" }");
break;
@@ -338,4 +345,55 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
break;
}
}
+
+ ///
+ /// 为生成字段选择不会与目标类型现有成员冲突的稳定名称。
+ ///
+ /// 当前需要补充 ContextAware 实现的目标类型。
+ /// 当前生成轮次应使用的上下文字段名集合。
+ private static GeneratedContextMemberNames CreateGeneratedContextMemberNames(INamedTypeSymbol symbol)
+ {
+ var reservedNames = new HashSet(
+ symbol.GetMembers()
+ .Where(static member => !member.IsImplicitlyDeclared)
+ .Select(static member => member.Name),
+ StringComparer.Ordinal);
+
+ return new GeneratedContextMemberNames(
+ AllocateGeneratedMemberName(reservedNames, "_gFrameworkContextAwareContext"),
+ AllocateGeneratedMemberName(reservedNames, "_gFrameworkContextAwareProvider"),
+ AllocateGeneratedMemberName(reservedNames, "_gFrameworkContextAwareSync"));
+ }
+
+ ///
+ /// 在固定前缀基础上按顺序追加数字后缀,直到找到可安全写入的成员名。
+ ///
+ /// 当前类型已占用或已为其他生成字段保留的名称集合。
+ /// 优先尝试的基础名称。
+ /// 本轮生成可以使用的唯一成员名。
+ private static string AllocateGeneratedMemberName(
+ ISet reservedNames,
+ string baseName)
+ {
+ if (reservedNames.Add(baseName))
+ return baseName;
+
+ for (var suffix = 1; ; suffix++)
+ {
+ var candidateName = $"{baseName}{suffix}";
+ if (reservedNames.Add(candidateName))
+ return candidateName;
+ }
+ }
+
+ ///
+ /// 描述一次 ContextAware 代码生成中选定的上下文字段名。
+ ///
+ /// 实例上下文缓存字段名。
+ /// 共享上下文提供者字段名。
+ /// 用于串行化访问的同步字段名。
+ private readonly record struct GeneratedContextMemberNames(
+ string ContextFieldName,
+ string ProviderFieldName,
+ string SyncFieldName);
}
diff --git a/GFramework.Core.Tests/Events/EasyEventsTests.cs b/GFramework.Core.Tests/Events/EasyEventsTests.cs
index 2b83ff08..ccec6b72 100644
--- a/GFramework.Core.Tests/Events/EasyEventsTests.cs
+++ b/GFramework.Core.Tests/Events/EasyEventsTests.cs
@@ -99,14 +99,14 @@ public class EasyEventsTests
}
///
- /// 测试 AddEvent 对重复事件类型给出状态冲突异常。
+ /// 测试 AddEvent 对重复事件类型保持兼容的参数异常类型。
///
[Test]
public void AddEvent_Should_Throw_When_Already_Registered()
{
_easyEvents.AddEvent>();
- Assert.Throws(() => _easyEvents.AddEvent>());
+ Assert.Throws(() => _easyEvents.AddEvent>());
}
///
diff --git a/GFramework.Core.Tests/Extensions/CollectionExtensionsTests.cs b/GFramework.Core.Tests/Extensions/CollectionExtensionsTests.cs
index 0635b5e3..9bbc1c4c 100644
--- a/GFramework.Core.Tests/Extensions/CollectionExtensionsTests.cs
+++ b/GFramework.Core.Tests/Extensions/CollectionExtensionsTests.cs
@@ -165,6 +165,20 @@ public class CollectionExtensionsTests
Assert.That(result["c"], Is.EqualTo(3));
}
+ ///
+ /// 测试ToDictionarySafe保持具体Dictionary返回类型,避免公开API继续收窄。
+ ///
+ [Test]
+ public void ToDictionarySafe_Should_Preserve_Concrete_Return_Type()
+ {
+ var method = typeof(GFramework.Core.Extensions.CollectionExtensions)
+ .GetMethods()
+ .Single(static method => method.Name == nameof(GFramework.Core.Extensions.CollectionExtensions.ToDictionarySafe));
+
+ Assert.That(method.ReturnType.IsGenericType, Is.True);
+ Assert.That(method.ReturnType.GetGenericTypeDefinition(), Is.EqualTo(typeof(Dictionary<,>)));
+ }
+
///
/// 测试ToDictionarySafe方法在存在重复键时覆盖前面的值
///
@@ -224,4 +238,4 @@ public class CollectionExtensionsTests
Assert.Throws(() =>
items.ToDictionarySafe<(string, int), string, int>(x => x.Item1, null!));
}
-}
\ No newline at end of file
+}
diff --git a/GFramework.Core.Tests/Logging/LoggingConfigurationTests.cs b/GFramework.Core.Tests/Logging/LoggingConfigurationTests.cs
index edae0876..3f88b4f5 100644
--- a/GFramework.Core.Tests/Logging/LoggingConfigurationTests.cs
+++ b/GFramework.Core.Tests/Logging/LoggingConfigurationTests.cs
@@ -39,6 +39,35 @@ public class LoggingConfigurationTests
Assert.That(config.LoggerLevels["GFramework.Core"], Is.EqualTo(LogLevel.Trace));
}
+ [Test]
+ public void Configuration_Collections_Should_Preserve_Public_Concrete_Types()
+ {
+ Assert.Multiple(() =>
+ {
+ Assert.That(
+ typeof(LoggingConfiguration).GetProperty(nameof(LoggingConfiguration.Appenders))!.PropertyType,
+ Is.EqualTo(typeof(List)));
+ Assert.That(
+ typeof(LoggingConfiguration).GetProperty(nameof(LoggingConfiguration.LoggerLevels))!.PropertyType,
+ Is.EqualTo(typeof(Dictionary)));
+ Assert.That(
+ typeof(FilterConfiguration).GetProperty(nameof(FilterConfiguration.Namespaces))!.PropertyType,
+ Is.EqualTo(typeof(List)));
+ Assert.That(
+ typeof(FilterConfiguration).GetProperty(nameof(FilterConfiguration.Filters))!.PropertyType,
+ Is.EqualTo(typeof(List)));
+ });
+ }
+
+ [Test]
+ public void LoggerLevels_Should_Remain_Case_Sensitive_By_Default()
+ {
+ var config = new LoggingConfiguration();
+ config.LoggerLevels["GFramework.Core"] = LogLevel.Info;
+
+ Assert.That(config.LoggerLevels.ContainsKey("gframework.core"), Is.False);
+ }
+
[Test]
public void LoadFromJsonString_WithInvalidJson_ShouldThrow()
{
diff --git a/GFramework.Core/Events/EasyEvents.cs b/GFramework.Core/Events/EasyEvents.cs
index adf42898..d21581c1 100644
--- a/GFramework.Core/Events/EasyEvents.cs
+++ b/GFramework.Core/Events/EasyEvents.cs
@@ -53,12 +53,14 @@ public class EasyEvents
/// 添加指定类型的事件到事件字典中
///
/// 事件类型,必须实现IEasyEvent接口且具有无参构造函数
- /// 当事件类型已存在时抛出。
+ /// 当事件类型已存在时抛出。
public void AddEvent() where T : IEvent, new()
{
if (!_mTypeEvents.TryAdd(typeof(T), new T()))
{
- throw new InvalidOperationException($"Event type {typeof(T).Name} already registered.");
+#pragma warning disable MA0015 // Preserve the public ArgumentException contract without inventing a fake parameter name.
+ throw new ArgumentException($"Event type {typeof(T).Name} already registered.");
+#pragma warning restore MA0015
}
}
diff --git a/GFramework.Core/Extensions/CollectionExtensions.cs b/GFramework.Core/Extensions/CollectionExtensions.cs
index 19d02671..8a57ce46 100644
--- a/GFramework.Core/Extensions/CollectionExtensions.cs
+++ b/GFramework.Core/Extensions/CollectionExtensions.cs
@@ -81,10 +81,12 @@ public static class CollectionExtensions
/// // dict["a"] == 3 (最后一个值)
///
///
- public static IDictionary ToDictionarySafe(
+#pragma warning disable MA0016 // Preserve the established concrete return type for public API compatibility.
+ public static Dictionary ToDictionarySafe(
this IEnumerable source,
Func keySelector,
Func valueSelector) where TKey : notnull
+#pragma warning restore MA0016
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(keySelector);
diff --git a/GFramework.Core/Logging/FilterConfiguration.cs b/GFramework.Core/Logging/FilterConfiguration.cs
index 74651da0..0dc02bec 100644
--- a/GFramework.Core/Logging/FilterConfiguration.cs
+++ b/GFramework.Core/Logging/FilterConfiguration.cs
@@ -20,10 +20,14 @@ public sealed class FilterConfiguration
///
/// 命名空间前缀列表(用于 Namespace 过滤器)。
///
- public IList? Namespaces { get; set; }
+#pragma warning disable MA0016 // Preserve the established concrete configuration API surface.
+ public List? Namespaces { get; set; }
+#pragma warning restore MA0016
///
/// 子过滤器列表(用于 Composite 过滤器)。
///
- public IList? Filters { get; set; }
+#pragma warning disable MA0016 // Preserve the established concrete configuration API surface.
+ public List? Filters { get; set; }
+#pragma warning restore MA0016
}
diff --git a/GFramework.Core/Logging/LoggingConfiguration.cs b/GFramework.Core/Logging/LoggingConfiguration.cs
index 0feedf6f..6bb164f7 100644
--- a/GFramework.Core/Logging/LoggingConfiguration.cs
+++ b/GFramework.Core/Logging/LoggingConfiguration.cs
@@ -15,11 +15,15 @@ public sealed class LoggingConfiguration
///
/// Appender 配置列表
///
- public IList Appenders { get; set; } = new List();
+#pragma warning disable MA0016 // Preserve the established concrete configuration API surface.
+ public List Appenders { get; set; } = new();
+#pragma warning restore MA0016
///
/// 特定 Logger 的日志级别配置
///
- public IDictionary LoggerLevels { get; set; } =
+#pragma warning disable MA0016 // Preserve the established concrete configuration API surface.
+ public Dictionary LoggerLevels { get; set; } =
new Dictionary(StringComparer.Ordinal);
+#pragma warning restore MA0016
}
diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.RuntimeTypeReferences.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.RuntimeTypeReferences.cs
index fcffb3e1..a10a6a9b 100644
--- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.RuntimeTypeReferences.cs
+++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.RuntimeTypeReferences.cs
@@ -105,9 +105,10 @@ public sealed partial class CqrsHandlerRegistryGenerator
out runtimeTypeReference);
}
- if (type is INamedTypeSymbol namedType)
+ if (type is INamedTypeSymbol namedType &&
+ TryCreateNamedRuntimeTypeReference(compilation, namedType, out var namedTypeReference))
{
- runtimeTypeReference = CreateNamedRuntimeTypeReference(compilation, namedType);
+ runtimeTypeReference = namedTypeReference;
return true;
}
@@ -162,17 +163,32 @@ public sealed partial class CqrsHandlerRegistryGenerator
///
/// 当前生成轮次的编译上下文。
/// 需要在运行时解析的命名类型。
- /// 适合写入生成注册器的命名类型运行时引用。
- private static RuntimeTypeReferenceSpec CreateNamedRuntimeTypeReference(
+ ///
+ /// 当方法返回 时,包含适合写入生成注册器的命名类型运行时引用;
+ /// 当返回 时,调用方应回退到更保守的注册路径。
+ ///
+ /// 当命名类型可安全编码为运行时引用时返回 。
+ private static bool TryCreateNamedRuntimeTypeReference(
Compilation compilation,
- INamedTypeSymbol namedType)
+ INamedTypeSymbol namedType,
+ out RuntimeTypeReferenceSpec? runtimeTypeReference)
{
if (SymbolEqualityComparer.Default.Equals(namedType.ContainingAssembly, compilation.Assembly))
- return RuntimeTypeReferenceSpec.FromReflectionLookup(GetReflectionTypeMetadataName(namedType));
+ {
+ runtimeTypeReference = RuntimeTypeReferenceSpec.FromReflectionLookup(GetReflectionTypeMetadataName(namedType));
+ return true;
+ }
- return RuntimeTypeReferenceSpec.FromExternalReflectionLookup(
+ if (namedType.ContainingAssembly is null)
+ {
+ runtimeTypeReference = null;
+ return false;
+ }
+
+ runtimeTypeReference = RuntimeTypeReferenceSpec.FromExternalReflectionLookup(
namedType.ContainingAssembly.Identity.ToString(),
GetReflectionTypeMetadataName(namedType));
+ return true;
}
///
@@ -214,6 +230,12 @@ public sealed partial class CqrsHandlerRegistryGenerator
return true;
}
+ if (genericTypeDefinition.ContainingAssembly is null)
+ {
+ genericTypeDefinitionReference = null;
+ return false;
+ }
+
genericTypeDefinitionReference = RuntimeTypeReferenceSpec.FromExternalReflectionLookup(
genericTypeDefinition.ContainingAssembly.Identity.ToString(),
GetReflectionTypeMetadataName(genericTypeDefinition));
diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
index d8768cd7..5ebf97e7 100644
--- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
+++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
@@ -143,7 +143,7 @@ public sealed partial class CqrsHandlerRegistryGenerator
if (sourceShape.HasExternalAssemblyTypeLookups)
{
builder.AppendLine();
- AppendReflectionHelpers(builder, sourceShape.HasExternalAssemblyTypeLookups);
+ AppendReflectionHelpers(builder);
}
builder.AppendLine("}");
@@ -792,53 +792,48 @@ public sealed partial class CqrsHandlerRegistryGenerator
return variableBaseName;
}
- private static void AppendReflectionHelpers(
- StringBuilder builder,
- bool includeExternalAssemblyTypeLookupHelpers)
+ private static void AppendReflectionHelpers(StringBuilder builder)
{
- if (includeExternalAssemblyTypeLookupHelpers)
- {
- builder.AppendLine(
- " private static global::System.Type? ResolveReferencedAssemblyType(string assemblyIdentity, string typeMetadataName)");
- builder.AppendLine(" {");
- builder.AppendLine(" var assembly = ResolveReferencedAssembly(assemblyIdentity);");
- builder.AppendLine(
- " return assembly?.GetType(typeMetadataName, throwOnError: false, ignoreCase: false);");
- builder.AppendLine(" }");
- builder.AppendLine();
- builder.AppendLine(
- " private static global::System.Reflection.Assembly? ResolveReferencedAssembly(string assemblyIdentity)");
- builder.AppendLine(" {");
- builder.AppendLine(" global::System.Reflection.AssemblyName targetAssemblyName;");
- builder.AppendLine(" try");
- builder.AppendLine(" {");
- builder.AppendLine(
- " targetAssemblyName = new global::System.Reflection.AssemblyName(assemblyIdentity);");
- builder.AppendLine(" }");
- builder.AppendLine(" catch");
- builder.AppendLine(" {");
- builder.AppendLine(" return null;");
- builder.AppendLine(" }");
- builder.AppendLine();
- builder.AppendLine(
- " foreach (var assembly in global::System.AppDomain.CurrentDomain.GetAssemblies())");
- builder.AppendLine(" {");
- builder.AppendLine(
- " if (global::System.Reflection.AssemblyName.ReferenceMatchesDefinition(targetAssemblyName, assembly.GetName()))");
- builder.AppendLine(" return assembly;");
- builder.AppendLine(" }");
- builder.AppendLine();
- builder.AppendLine(" try");
- builder.AppendLine(" {");
- builder.AppendLine(
- " return global::System.Reflection.Assembly.Load(targetAssemblyName);");
- builder.AppendLine(" }");
- builder.AppendLine(" catch");
- builder.AppendLine(" {");
- builder.AppendLine(" return null;");
- builder.AppendLine(" }");
- builder.AppendLine(" }");
- }
+ builder.AppendLine(
+ " private static global::System.Type? ResolveReferencedAssemblyType(string assemblyIdentity, string typeMetadataName)");
+ builder.AppendLine(" {");
+ builder.AppendLine(" var assembly = ResolveReferencedAssembly(assemblyIdentity);");
+ builder.AppendLine(
+ " return assembly?.GetType(typeMetadataName, throwOnError: false, ignoreCase: false);");
+ builder.AppendLine(" }");
+ builder.AppendLine();
+ builder.AppendLine(
+ " private static global::System.Reflection.Assembly? ResolveReferencedAssembly(string assemblyIdentity)");
+ builder.AppendLine(" {");
+ builder.AppendLine(" global::System.Reflection.AssemblyName targetAssemblyName;");
+ builder.AppendLine(" try");
+ builder.AppendLine(" {");
+ builder.AppendLine(
+ " targetAssemblyName = new global::System.Reflection.AssemblyName(assemblyIdentity);");
+ builder.AppendLine(" }");
+ builder.AppendLine(" catch");
+ builder.AppendLine(" {");
+ builder.AppendLine(" return null;");
+ builder.AppendLine(" }");
+ builder.AppendLine();
+ builder.AppendLine(
+ " foreach (var assembly in global::System.AppDomain.CurrentDomain.GetAssemblies())");
+ builder.AppendLine(" {");
+ builder.AppendLine(
+ " if (global::System.Reflection.AssemblyName.ReferenceMatchesDefinition(targetAssemblyName, assembly.GetName()))");
+ builder.AppendLine(" return assembly;");
+ builder.AppendLine(" }");
+ builder.AppendLine();
+ builder.AppendLine(" try");
+ builder.AppendLine(" {");
+ builder.AppendLine(
+ " return global::System.Reflection.Assembly.Load(targetAssemblyName);");
+ builder.AppendLine(" }");
+ builder.AppendLine(" catch");
+ builder.AppendLine(" {");
+ builder.AppendLine(" return null;");
+ builder.AppendLine(" }");
+ builder.AppendLine(" }");
}
private static string EscapeStringLiteral(string value)
diff --git a/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs b/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs
index d0926c57..c8499bbe 100644
--- a/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs
+++ b/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs
@@ -134,6 +134,10 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
{
text = file.GetText(cancellationToken);
}
+ catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+ {
+ throw;
+ }
catch (Exception exception)
{
text = null;
diff --git a/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs
index 2da0ffa9..4ef4bbad 100644
--- a/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs
@@ -6,6 +6,32 @@ namespace GFramework.SourceGenerators.Tests.Config;
[TestFixture]
public class SchemaConfigGeneratorTests
{
+ ///
+ /// 验证 AdditionalFiles 读取被取消时会向上传播取消,而不是伪造成 schema 诊断。
+ ///
+ [Test]
+ public void Run_Should_Propagate_Cancellation_When_AdditionalText_Read_Is_Cancelled()
+ {
+ var method = typeof(global::GFramework.Game.SourceGenerators.Config.SchemaConfigGenerator)
+ .GetMethod(
+ "TryReadSchemaText",
+ global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static);
+ var cancellationTokenSource = new CancellationTokenSource();
+ cancellationTokenSource.Cancel();
+ var invocationArguments = new object?[]
+ {
+ new ThrowingAdditionalText("monster.schema.json"),
+ cancellationTokenSource.Token,
+ null,
+ null
+ };
+
+ var exception = Assert.Throws(() =>
+ method!.Invoke(null, invocationArguments));
+
+ Assert.That(exception!.InnerException, Is.TypeOf());
+ }
+
///
/// 验证缺失必填 id 字段时会产生命名明确的诊断。
///
@@ -46,6 +72,30 @@ public class SchemaConfigGeneratorTests
});
}
+ ///
+ /// 用于模拟 AdditionalFiles 读取阶段直接收到取消请求的测试桩。
+ ///
+ private sealed class ThrowingAdditionalText : AdditionalText
+ {
+ ///
+ /// 创建一个在读取时抛出取消异常的 AdditionalText。
+ ///
+ /// 虚拟 schema 路径。
+ public ThrowingAdditionalText(string path)
+ {
+ Path = path;
+ }
+
+ ///
+ public override string Path { get; }
+
+ ///
+ public override SourceText GetText(CancellationToken cancellationToken = default)
+ {
+ throw new OperationCanceledException(cancellationToken);
+ }
+ }
+
///
/// 验证空字符串 const 不会在生成 XML 文档时被当成“缺失约束”跳过。
///
diff --git a/GFramework.SourceGenerators.Tests/Config/SchemaGeneratorTestDriver.cs b/GFramework.SourceGenerators.Tests/Config/SchemaGeneratorTestDriver.cs
index c8f5a4e4..0909d72e 100644
--- a/GFramework.SourceGenerators.Tests/Config/SchemaGeneratorTestDriver.cs
+++ b/GFramework.SourceGenerators.Tests/Config/SchemaGeneratorTestDriver.cs
@@ -20,6 +20,23 @@ public static class SchemaGeneratorTestDriver
public static GeneratorDriverRunResult Run(
string source,
params (string path, string content)[] additionalFiles)
+ {
+ return Run(
+ source,
+ additionalFiles
+ .Select(static item => (AdditionalText)new InMemoryAdditionalText(item.path, item.content))
+ .ToArray());
+ }
+
+ ///
+ /// 运行 schema 配置生成器,并允许测试自定义 AdditionalText 行为。
+ ///
+ /// 测试用源码。
+ /// 自定义 AdditionalText 集合。
+ /// 生成器运行结果。
+ public static GeneratorDriverRunResult Run(
+ string source,
+ params AdditionalText[] additionalTexts)
{
var syntaxTree = CSharpSyntaxTree.ParseText(source);
var compilation = CSharpCompilation.Create(
@@ -28,13 +45,9 @@ public static class SchemaGeneratorTestDriver
GetMetadataReferences(),
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
- var additionalTexts = additionalFiles
- .Select(static item => (AdditionalText)new InMemoryAdditionalText(item.path, item.content))
- .ToImmutableArray();
-
GeneratorDriver driver = CSharpGeneratorDriver.Create(
generators: new[] { new SchemaConfigGenerator().AsSourceGenerator() },
- additionalTexts: additionalTexts,
+ additionalTexts: additionalTexts.ToImmutableArray(),
parseOptions: (CSharpParseOptions)syntaxTree.Options);
driver = driver.RunGenerators(compilation);
diff --git a/GFramework.SourceGenerators.Tests/Rule/ContextAwareGeneratorSnapshotTests.cs b/GFramework.SourceGenerators.Tests/Rule/ContextAwareGeneratorSnapshotTests.cs
index 7a57c668..e9f8510b 100644
--- a/GFramework.SourceGenerators.Tests/Rule/ContextAwareGeneratorSnapshotTests.cs
+++ b/GFramework.SourceGenerators.Tests/Rule/ContextAwareGeneratorSnapshotTests.cs
@@ -154,6 +154,9 @@ public class ContextAwareGeneratorSnapshotTests
private readonly string _context = "user-field";
private static readonly string _contextProvider = "user-provider";
private static readonly object _contextSync = new();
+ private IArchitectureContext? _gFrameworkContextAwareContext;
+ private static IArchitectureContextProvider? _gFrameworkContextAwareProvider;
+ private static readonly object _gFrameworkContextAwareSync = new();
}
}
""";
diff --git a/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/CollisionProneRule.ContextAware.g.cs b/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/CollisionProneRule.ContextAware.g.cs
index 99e6e492..7b56a1fa 100644
--- a/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/CollisionProneRule.ContextAware.g.cs
+++ b/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/CollisionProneRule.ContextAware.g.cs
@@ -10,14 +10,14 @@ namespace TestApp;
/// 生成代码会在实例级缓存首次解析到的上下文,并在未显式配置提供者时回退到 。
/// 同一生成类型的所有实例共享一个静态上下文提供者;切换或重置提供者只会影响尚未缓存上下文的新实例或未初始化实例,
/// 已缓存的实例上下文需要通过 显式覆盖。
-/// 与手动继承 的路径相比,生成实现会使用 _gFrameworkContextAwareSync 协调惰性初始化、provider 切换和显式上下文注入;
+/// 与手动继承 的路径相比,生成实现会使用 _gFrameworkContextAwareSync1 协调惰性初始化、provider 切换和显式上下文注入;
/// 则保持无锁的实例级缓存语义,更适合已经由调用方线程模型保证串行访问的简单场景。
///
partial class CollisionProneRule : global::GFramework.Core.Abstractions.Rule.IContextAware
{
- private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _gFrameworkContextAwareContext;
- private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _gFrameworkContextAwareProvider;
- private static readonly object _gFrameworkContextAwareSync = new();
+ private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _gFrameworkContextAwareContext1;
+ private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _gFrameworkContextAwareProvider1;
+ private static readonly object _gFrameworkContextAwareSync1 = new();
///
/// 获取当前实例绑定的架构上下文。
@@ -27,26 +27,20 @@ partial class CollisionProneRule : global::GFramework.Core.Abstractions.Rule.ICo
/// 当静态提供者尚未配置时,生成代码会回退到 。
/// 一旦某个实例成功缓存上下文,后续
/// 或 不会自动清除此缓存;如需覆盖,请显式调用 IContextAware.SetContext(...)。
- /// 当前实现还假设 可在持有 _gFrameworkContextAwareSync 时安全执行;
+ /// 当前实现还假设 可在持有 _gFrameworkContextAwareSync1 时安全执行;
/// 自定义 provider 不应在该调用链内重新进入当前类型的 provider 配置 API,且应避免引入与外部全局锁相互等待的锁顺序。
///
protected global::GFramework.Core.Abstractions.Architectures.IArchitectureContext Context
{
get
{
- var context = _gFrameworkContextAwareContext;
- if (context is not null)
- {
- return context;
- }
-
// 在同一个同步域内协调懒加载与 provider 切换,避免读取到被并发重置的空提供者。
- // provider 的 GetContext() 会在持有 _gFrameworkContextAwareSync 时执行;自定义 provider 必须避免在该调用链内回调 SetContextProvider/ResetContextProvider 或形成反向锁顺序。
- lock (_gFrameworkContextAwareSync)
+ // provider 的 GetContext() 会在持有 _gFrameworkContextAwareSync1 时执行;自定义 provider 必须避免在该调用链内回调 SetContextProvider/ResetContextProvider 或形成反向锁顺序。
+ lock (_gFrameworkContextAwareSync1)
{
- _gFrameworkContextAwareProvider ??= new global::GFramework.Core.Architectures.GameContextProvider();
- _gFrameworkContextAwareContext ??= _gFrameworkContextAwareProvider.GetContext();
- return _gFrameworkContextAwareContext;
+ _gFrameworkContextAwareProvider1 ??= new global::GFramework.Core.Architectures.GameContextProvider();
+ _gFrameworkContextAwareContext1 ??= _gFrameworkContextAwareProvider1.GetContext();
+ return _gFrameworkContextAwareContext1;
}
}
}
@@ -64,9 +58,9 @@ partial class CollisionProneRule : global::GFramework.Core.Abstractions.Rule.ICo
public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider)
{
global::System.ArgumentNullException.ThrowIfNull(provider);
- lock (_gFrameworkContextAwareSync)
+ lock (_gFrameworkContextAwareSync1)
{
- _gFrameworkContextAwareProvider = provider;
+ _gFrameworkContextAwareProvider1 = provider;
}
}
@@ -80,18 +74,18 @@ partial class CollisionProneRule : global::GFramework.Core.Abstractions.Rule.ICo
///
public static void ResetContextProvider()
{
- lock (_gFrameworkContextAwareSync)
+ lock (_gFrameworkContextAwareSync1)
{
- _gFrameworkContextAwareProvider = null;
+ _gFrameworkContextAwareProvider1 = null;
}
}
void global::GFramework.Core.Abstractions.Rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.Architectures.IArchitectureContext context)
{
// 与 Context getter 共享同一同步协议,避免显式注入被并发懒加载覆盖。
- lock (_gFrameworkContextAwareSync)
+ lock (_gFrameworkContextAwareSync1)
{
- _gFrameworkContextAwareContext = context;
+ _gFrameworkContextAwareContext1 = context;
}
}
diff --git a/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/MyRule.ContextAware.g.cs b/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/MyRule.ContextAware.g.cs
index 0788b52b..4072ea3d 100644
--- a/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/MyRule.ContextAware.g.cs
+++ b/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/MyRule.ContextAware.g.cs
@@ -34,12 +34,6 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware
{
get
{
- var context = _gFrameworkContextAwareContext;
- if (context is not null)
- {
- return context;
- }
-
// 在同一个同步域内协调懒加载与 provider 切换,避免读取到被并发重置的空提供者。
// provider 的 GetContext() 会在持有 _gFrameworkContextAwareSync 时执行;自定义 provider 必须避免在该调用链内回调 SetContextProvider/ResetContextProvider 或形成反向锁顺序。
lock (_gFrameworkContextAwareSync)
diff --git a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md
index e0620bc3..97030a1e 100644
--- a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md
+++ b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md
@@ -7,8 +7,8 @@
## 当前恢复点
-- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-021`
-- 当前阶段:`Phase 21`
+- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-022`
+- 当前阶段:`Phase 22`
- 当前焦点:
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
@@ -20,10 +20,11 @@
- 已完成 `SchemaConfigGenerator.cs` 的第一批 `MA0051` 结构拆分:schema 入口解析、属性解析、schema 遍历、数组属性解析、
约束文档生成与若干生成代码发射 helper 已拆出语义阶段
- 已完成当前 PR #269 review follow-up:`CqrsHandlerRegistryGenerator` 按职责拆分为 partial 生成器文件,
- `ContextAwareGenerator` 改用稳定前缀字段并补上 provider null 防御,`Option` 补齐 `` 契约说明
- - `LoggingConfiguration`、`FilterConfiguration` 与 `CollectionExtensions` 已改用集合抽象接口,并保留内部具体集合默认值
+ `ContextAwareGenerator` 已补上字段名去冲突与锁内读取修正,`Option` 补齐 `` 契约说明
+ - 已完成当前 PR #269 第二轮 follow-up:恢复 `EasyEvents`、`CollectionExtensions`、`LoggingConfiguration` 与
+ `FilterConfiguration` 的公共 API 兼容形状,并将 analyzer 兼容性处理收敛到局部 pragma
- `CoroutineScheduler` 的 tag/group 字典已显式使用 `StringComparer.Ordinal`,保持既有区分大小写语义
- - `EasyEvents.AddEvent()` 的重复注册路径已改为状态冲突异常,避免把泛型类型参数伪装成方法参数名
+ - `EasyEvents.AddEvent()` 的重复注册路径已恢复为 `ArgumentException`,以保持既有异常契约
- `Option` 已声明 `IEquatable