From a7a3eca40dff3c10e400a805dadc3b8f34c177ab Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Sat, 25 Apr 2026 14:26:49 +0800
Subject: [PATCH] =?UTF-8?q?fix(pr-review):=20=E6=94=B6=E6=95=9BPR=E5=BB=BA?=
=?UTF-8?q?=E8=AE=AE=E5=B9=B6=E4=BF=AE=E5=A4=8D=E6=9E=84=E5=BB=BA=E9=AA=8C?=
=?UTF-8?q?=E8=AF=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 PR #288 中经本地复核后仍成立的 Core、Game 与测试建议
- 更新 WSL 标准 dotnet build 验证路径并确认 Release 构建可通过
- 补充 analyzer-warning-reduction 跟踪文档记录本轮结论与恢复点
---
.../ArchitectureServicesTests.cs | 8 ++--
.../Architectures/GameContextTests.cs | 20 ++++----
.../RegistryInitializationHookBaseTests.cs | 2 +-
.../Coroutine/TaskCoroutineExtensionsTests.cs | 2 -
.../Coroutine/WaitForTaskTests.cs | 3 +-
.../Logging/RollingFileAppenderTests.cs | 2 +-
GFramework.Core/Extensions/AsyncExtensions.cs | 21 ++-------
.../ContextAwareCommandExtensions.cs | 44 ++++--------------
.../Extensions/ContextAwareEventExtensions.cs | 38 +++------------
.../Extensions/ContextAwareQueryExtensions.cs | 22 ++-------
.../Extensions/NumericExtensions.cs | 17 ++-----
.../Extensions/StringExtensions.cs | 22 ++-------
.../StateManagement/StoreBuilder.cs | 15 ++----
.../StateManagement/StoreSelection.cs | 11 +----
GFramework.Game/Scene/SceneRouterBase.cs | 41 +++++++++--------
GFramework.Game/Storage/FileStorage.cs | 4 +-
GFramework.Game/Storage/ScopedStorage.cs | 4 +-
.../analyzer-warning-reduction-tracking.md | 46 +++++++++++++------
.../analyzer-warning-reduction-trace.md | 33 +++++++++++++
19 files changed, 143 insertions(+), 212 deletions(-)
diff --git a/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs
index 4a520097..f1474403 100644
--- a/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs
+++ b/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs
@@ -366,7 +366,7 @@ public class TestArchitectureContextV3 : IArchitectureContext
/// 要发送的命令。
/// 取消令牌。
/// 命令响应任务。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public ValueTask SendCommandAsync(
GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command,
CancellationToken cancellationToken = default)
@@ -380,7 +380,7 @@ public class TestArchitectureContextV3 : IArchitectureContext
/// 命令响应类型。
/// 要发送的命令。
/// 命令响应。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public TResponse SendCommand(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command)
{
throw new NotSupportedException();
@@ -393,7 +393,7 @@ public class TestArchitectureContextV3 : IArchitectureContext
/// 要发送的查询。
/// 取消令牌。
/// 查询结果任务。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public ValueTask SendQueryAsync(
GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query,
CancellationToken cancellationToken = default)
@@ -407,7 +407,7 @@ public class TestArchitectureContextV3 : IArchitectureContext
/// 查询结果类型。
/// 要发送的查询。
/// 查询结果。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public TResponse SendQuery(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query)
{
throw new NotSupportedException();
diff --git a/GFramework.Core.Tests/Architectures/GameContextTests.cs b/GFramework.Core.Tests/Architectures/GameContextTests.cs
index 4f9b99e3..fd6e2bbb 100644
--- a/GFramework.Core.Tests/Architectures/GameContextTests.cs
+++ b/GFramework.Core.Tests/Architectures/GameContextTests.cs
@@ -401,7 +401,7 @@ public class TestArchitectureContext : IArchitectureContext
/// 要发送的请求。
/// 取消令牌。
/// 请求响应任务。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public ValueTask SendRequestAsync(IRequest request,
CancellationToken cancellationToken = default)
{
@@ -414,7 +414,7 @@ public class TestArchitectureContext : IArchitectureContext
/// 响应类型。
/// 要发送的请求。
/// 请求响应。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public TResponse SendRequest(IRequest request)
{
throw new NotSupportedException();
@@ -427,7 +427,7 @@ public class TestArchitectureContext : IArchitectureContext
/// 要发送的命令。
/// 取消令牌。
/// 命令响应任务。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public ValueTask SendCommandAsync(
GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command,
CancellationToken cancellationToken = default)
@@ -441,7 +441,7 @@ public class TestArchitectureContext : IArchitectureContext
/// 命令响应类型。
/// 要发送的命令。
/// 命令响应。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public TResponse SendCommand(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command)
{
throw new NotSupportedException();
@@ -454,7 +454,7 @@ public class TestArchitectureContext : IArchitectureContext
/// 要发送的查询。
/// 取消令牌。
/// 查询结果任务。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public ValueTask SendQueryAsync(
GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query,
CancellationToken cancellationToken = default)
@@ -468,7 +468,7 @@ public class TestArchitectureContext : IArchitectureContext
/// 查询结果类型。
/// 要发送的查询。
/// 查询结果。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public TResponse SendQuery(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query)
{
throw new NotSupportedException();
@@ -481,7 +481,7 @@ public class TestArchitectureContext : IArchitectureContext
/// 要发布的通知。
/// 取消令牌。
/// 通知发布任务。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public ValueTask PublishAsync(TNotification notification,
CancellationToken cancellationToken = default) where TNotification : INotification
{
@@ -495,7 +495,7 @@ public class TestArchitectureContext : IArchitectureContext
/// 流式请求。
/// 取消令牌。
/// 异步响应流。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public IAsyncEnumerable CreateStream(
IStreamRequest request,
CancellationToken cancellationToken = default)
@@ -510,7 +510,7 @@ public class TestArchitectureContext : IArchitectureContext
/// 要发送的命令。
/// 取消令牌。
/// 命令发送任务。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public ValueTask SendAsync(TCommand command, CancellationToken cancellationToken = default)
where TCommand : IRequest
{
@@ -524,7 +524,7 @@ public class TestArchitectureContext : IArchitectureContext
/// 要发送的请求。
/// 取消令牌。
/// 请求响应任务。
- /// 该测试桩未实现此成员。
+ /// 该测试桩不支持此成员。
public ValueTask SendAsync(IRequest command,
CancellationToken cancellationToken = default)
{
diff --git a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs
index 4fdb4398..b3a3975f 100644
--- a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs
+++ b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs
@@ -287,7 +287,7 @@ public class TestArchitectureContextWithRegistry : TestArchitectureContext
_registry = registry;
}
- public override TUtility GetUtility()
+ public override TUtility? GetUtility() where TUtility : class
{
if (typeof(TUtility) == typeof(TestRegistry))
{
diff --git a/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs b/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs
index 012fa13e..9c46223c 100644
--- a/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs
+++ b/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs
@@ -66,8 +66,6 @@ public class TaskCoroutineExtensionsTests
var task = Task.FromResult(42);
var instruction = task.AsCoroutineInstruction();
- task.ConfigureAwait(false).GetAwaiter().GetResult();
-
Assert.That(instruction.Result, Is.EqualTo(42));
}
diff --git a/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs b/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs
index 73789a5d..c8d50b39 100644
--- a/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs
+++ b/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs
@@ -291,8 +291,7 @@ public class WaitForTaskTests
var wait = new WaitForTask(task);
await task.ConfigureAwait(false);
-
- Task.Delay(100).Wait();
+ await Task.Delay(100).ConfigureAwait(false);
Assert.That(wait.IsDone, Is.True);
Assert.That(wait.Result, Is.EqualTo(expectedValue));
diff --git a/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs b/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs
index e487f188..5cce91e3 100644
--- a/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs
+++ b/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs
@@ -68,7 +68,7 @@ public class RollingFileAppenderTests
}
// 检查是否生成了多个文件
- var files = Directory.GetFiles(_testDir, "*.log").OrderBy(f => f, System.StringComparer.Ordinal).ToArray();
+ var files = Directory.GetFiles(_testDir, "*.log");
Assert.That(files.Length, Is.GreaterThan(1));
}
diff --git a/GFramework.Core/Extensions/AsyncExtensions.cs b/GFramework.Core/Extensions/AsyncExtensions.cs
index 246252ca..47124086 100644
--- a/GFramework.Core/Extensions/AsyncExtensions.cs
+++ b/GFramework.Core/Extensions/AsyncExtensions.cs
@@ -28,10 +28,7 @@ public static class AsyncExtensions
TimeSpan timeout,
CancellationToken cancellationToken = default)
{
- if (taskFactory is null)
- {
- throw new ArgumentNullException(nameof(taskFactory));
- }
+ ArgumentNullException.ThrowIfNull(taskFactory);
// linkedCts 同时响应:超时 + 外部取消
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
@@ -74,10 +71,7 @@ public static class AsyncExtensions
TimeSpan timeout,
CancellationToken cancellationToken = default)
{
- if (taskFactory is null)
- {
- throw new ArgumentNullException(nameof(taskFactory));
- }
+ ArgumentNullException.ThrowIfNull(taskFactory);
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
linkedCts.CancelAfter(timeout);
@@ -119,15 +113,8 @@ public static class AsyncExtensions
///
public static async Task WithFallbackAsync(this Task task, Func fallback)
{
- if (task is null)
- {
- throw new ArgumentNullException(nameof(task));
- }
-
- if (fallback is null)
- {
- throw new ArgumentNullException(nameof(fallback));
- }
+ ArgumentNullException.ThrowIfNull(task);
+ ArgumentNullException.ThrowIfNull(fallback);
try
{
diff --git a/GFramework.Core/Extensions/ContextAwareCommandExtensions.cs b/GFramework.Core/Extensions/ContextAwareCommandExtensions.cs
index 5a98d179..49676e56 100644
--- a/GFramework.Core/Extensions/ContextAwareCommandExtensions.cs
+++ b/GFramework.Core/Extensions/ContextAwareCommandExtensions.cs
@@ -19,15 +19,8 @@ public static class ContextAwareCommandExtensions
public static TResult SendCommand(this IContextAware contextAware,
ICommand command)
{
- if (contextAware is null)
- {
- throw new ArgumentNullException(nameof(contextAware));
- }
-
- if (command is null)
- {
- throw new ArgumentNullException(nameof(command));
- }
+ ArgumentNullException.ThrowIfNull(contextAware);
+ ArgumentNullException.ThrowIfNull(command);
var context = contextAware.GetContext();
return context.SendCommand(command);
@@ -41,15 +34,8 @@ public static class ContextAwareCommandExtensions
/// 当 contextAware 或 command 为 null 时抛出
public static void SendCommand(this IContextAware contextAware, ICommand command)
{
- if (contextAware is null)
- {
- throw new ArgumentNullException(nameof(contextAware));
- }
-
- if (command is null)
- {
- throw new ArgumentNullException(nameof(command));
- }
+ ArgumentNullException.ThrowIfNull(contextAware);
+ ArgumentNullException.ThrowIfNull(command);
var context = contextAware.GetContext();
context.SendCommand(command);
@@ -64,15 +50,8 @@ public static class ContextAwareCommandExtensions
/// 当 contextAware 或 command 为 null 时抛出
public static async Task SendCommandAsync(this IContextAware contextAware, IAsyncCommand command)
{
- if (contextAware is null)
- {
- throw new ArgumentNullException(nameof(contextAware));
- }
-
- if (command is null)
- {
- throw new ArgumentNullException(nameof(command));
- }
+ ArgumentNullException.ThrowIfNull(contextAware);
+ ArgumentNullException.ThrowIfNull(command);
var context = contextAware.GetContext();
await context.SendCommandAsync(command).ConfigureAwait(false);
@@ -89,15 +68,8 @@ public static class ContextAwareCommandExtensions
public static async Task SendCommandAsync(this IContextAware contextAware,
IAsyncCommand command)
{
- if (contextAware is null)
- {
- throw new ArgumentNullException(nameof(contextAware));
- }
-
- if (command is null)
- {
- throw new ArgumentNullException(nameof(command));
- }
+ ArgumentNullException.ThrowIfNull(contextAware);
+ ArgumentNullException.ThrowIfNull(command);
var context = contextAware.GetContext();
return await context.SendCommandAsync(command).ConfigureAwait(false);
diff --git a/GFramework.Core/Extensions/ContextAwareEventExtensions.cs b/GFramework.Core/Extensions/ContextAwareEventExtensions.cs
index f1e35732..491c5230 100644
--- a/GFramework.Core/Extensions/ContextAwareEventExtensions.cs
+++ b/GFramework.Core/Extensions/ContextAwareEventExtensions.cs
@@ -16,10 +16,7 @@ public static class ContextAwareEventExtensions
/// 当 contextAware 为 null 时抛出
public static void SendEvent(this IContextAware contextAware) where TEvent : new()
{
- if (contextAware is null)
- {
- throw new ArgumentNullException(nameof(contextAware));
- }
+ ArgumentNullException.ThrowIfNull(contextAware);
var context = contextAware.GetContext();
context.SendEvent();
@@ -34,15 +31,8 @@ public static class ContextAwareEventExtensions
/// 当 contextAware 或 e 为 null 时抛出
public static void SendEvent(this IContextAware contextAware, TEvent e) where TEvent : class
{
- if (contextAware is null)
- {
- throw new ArgumentNullException(nameof(contextAware));
- }
-
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
+ ArgumentNullException.ThrowIfNull(contextAware);
+ ArgumentNullException.ThrowIfNull(e);
var context = contextAware.GetContext();
context.SendEvent(e);
@@ -57,15 +47,8 @@ public static class ContextAwareEventExtensions
/// 事件注销接口
public static IUnRegister RegisterEvent(this IContextAware contextAware, Action handler)
{
- if (contextAware is null)
- {
- throw new ArgumentNullException(nameof(contextAware));
- }
-
- if (handler is null)
- {
- throw new ArgumentNullException(nameof(handler));
- }
+ ArgumentNullException.ThrowIfNull(contextAware);
+ ArgumentNullException.ThrowIfNull(handler);
var context = contextAware.GetContext();
return context.RegisterEvent(handler);
@@ -79,15 +62,8 @@ public static class ContextAwareEventExtensions
/// 之前绑定的事件处理器
public static void UnRegisterEvent(this IContextAware contextAware, Action onEvent)
{
- if (contextAware is null)
- {
- throw new ArgumentNullException(nameof(contextAware));
- }
-
- if (onEvent is null)
- {
- throw new ArgumentNullException(nameof(onEvent));
- }
+ ArgumentNullException.ThrowIfNull(contextAware);
+ ArgumentNullException.ThrowIfNull(onEvent);
var context = contextAware.GetContext();
context.UnRegisterEvent(onEvent);
diff --git a/GFramework.Core/Extensions/ContextAwareQueryExtensions.cs b/GFramework.Core/Extensions/ContextAwareQueryExtensions.cs
index 07e6aa7a..63ad260c 100644
--- a/GFramework.Core/Extensions/ContextAwareQueryExtensions.cs
+++ b/GFramework.Core/Extensions/ContextAwareQueryExtensions.cs
@@ -18,15 +18,8 @@ public static class ContextAwareQueryExtensions
/// 当 contextAware 或 query 为 null 时抛出
public static TResult SendQuery(this IContextAware contextAware, IQuery query)
{
- if (contextAware is null)
- {
- throw new ArgumentNullException(nameof(contextAware));
- }
-
- if (query is null)
- {
- throw new ArgumentNullException(nameof(query));
- }
+ ArgumentNullException.ThrowIfNull(contextAware);
+ ArgumentNullException.ThrowIfNull(query);
var context = contextAware.GetContext();
return context.SendQuery(query);
@@ -44,15 +37,8 @@ public static class ContextAwareQueryExtensions
public static async Task SendQueryAsync(this IContextAware contextAware,
IAsyncQuery query)
{
- if (contextAware is null)
- {
- throw new ArgumentNullException(nameof(contextAware));
- }
-
- if (query is null)
- {
- throw new ArgumentNullException(nameof(query));
- }
+ ArgumentNullException.ThrowIfNull(contextAware);
+ ArgumentNullException.ThrowIfNull(query);
var context = contextAware.GetContext();
return await context.SendQueryAsync(query).ConfigureAwait(false);
diff --git a/GFramework.Core/Extensions/NumericExtensions.cs b/GFramework.Core/Extensions/NumericExtensions.cs
index c00f0044..c0637310 100644
--- a/GFramework.Core/Extensions/NumericExtensions.cs
+++ b/GFramework.Core/Extensions/NumericExtensions.cs
@@ -25,20 +25,9 @@ public static class NumericExtensions
///
public static bool Between(this T value, T min, T max, bool inclusive = true) where T : IComparable
{
- if (value is null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-
- if (min is null)
- {
- throw new ArgumentNullException(nameof(min));
- }
-
- if (max is null)
- {
- throw new ArgumentNullException(nameof(max));
- }
+ ArgumentNullException.ThrowIfNull(value);
+ ArgumentNullException.ThrowIfNull(min);
+ ArgumentNullException.ThrowIfNull(max);
if (min.CompareTo(max) > 0)
{
diff --git a/GFramework.Core/Extensions/StringExtensions.cs b/GFramework.Core/Extensions/StringExtensions.cs
index 616c3a83..914dc855 100644
--- a/GFramework.Core/Extensions/StringExtensions.cs
+++ b/GFramework.Core/Extensions/StringExtensions.cs
@@ -38,15 +38,8 @@ public static class StringExtensions
///
public static string Truncate(this string str, int maxLength, string suffix = "...")
{
- if (str is null)
- {
- throw new ArgumentNullException(nameof(str));
- }
-
- if (suffix is null)
- {
- throw new ArgumentNullException(nameof(suffix));
- }
+ ArgumentNullException.ThrowIfNull(str);
+ ArgumentNullException.ThrowIfNull(suffix);
if (maxLength < suffix.Length)
{
@@ -77,15 +70,8 @@ public static class StringExtensions
///
public static string Join(this IEnumerable values, string separator)
{
- if (values is null)
- {
- throw new ArgumentNullException(nameof(values));
- }
-
- if (separator is null)
- {
- throw new ArgumentNullException(nameof(separator));
- }
+ ArgumentNullException.ThrowIfNull(values);
+ ArgumentNullException.ThrowIfNull(separator);
return string.Join(separator, values);
}
diff --git a/GFramework.Core/StateManagement/StoreBuilder.cs b/GFramework.Core/StateManagement/StoreBuilder.cs
index bf2565ea..50043a11 100644
--- a/GFramework.Core/StateManagement/StoreBuilder.cs
+++ b/GFramework.Core/StateManagement/StoreBuilder.cs
@@ -39,10 +39,7 @@ public sealed class StoreBuilder : IStoreBuilder
/// 当前构建器实例。
public IStoreBuilder UseMiddleware(IStoreMiddleware middleware)
{
- if (middleware is null)
- {
- throw new ArgumentNullException(nameof(middleware));
- }
+ ArgumentNullException.ThrowIfNull(middleware);
_configurators.Add(store => store.UseMiddleware(middleware));
return this;
@@ -112,10 +109,7 @@ public sealed class StoreBuilder : IStoreBuilder
/// 当前构建器实例。
public IStoreBuilder AddReducer(Func reducer)
{
- if (reducer is null)
- {
- throw new ArgumentNullException(nameof(reducer));
- }
+ ArgumentNullException.ThrowIfNull(reducer);
_configurators.Add(store => store.RegisterReducer(reducer));
return this;
@@ -129,10 +123,7 @@ public sealed class StoreBuilder : IStoreBuilder
/// 当前构建器实例。
public IStoreBuilder AddReducer(IReducer reducer)
{
- if (reducer is null)
- {
- throw new ArgumentNullException(nameof(reducer));
- }
+ ArgumentNullException.ThrowIfNull(reducer);
_configurators.Add(store => store.RegisterReducer(reducer));
return this;
diff --git a/GFramework.Core/StateManagement/StoreSelection.cs b/GFramework.Core/StateManagement/StoreSelection.cs
index 867831b0..0f417409 100644
--- a/GFramework.Core/StateManagement/StoreSelection.cs
+++ b/GFramework.Core/StateManagement/StoreSelection.cs
@@ -24,18 +24,11 @@ public sealed class StoreSelection : IReadonlyBindablePropert
///
private readonly List _listeners = [];
- ///
- /// 保护监听器集合和底层 Store 订阅句柄的同步锁。
- ///
#if NET9_0_OR_GREATER
- ///
- /// net9.0 及以上目标使用专用 Lock,以满足分析器对专用同步原语的建议。
- ///
+ // net9.0 及以上目标使用专用 Lock,以满足分析器对专用同步原语的建议。
private readonly System.Threading.Lock _lock = new();
#else
- ///
- /// net8.0 目标仍回退到 object 锁,以保持多目标编译兼容性。
- ///
+ // net8.0 目标仍回退到 object 锁,以保持多目标编译兼容性。
private readonly object _lock = new();
#endif
diff --git a/GFramework.Game/Scene/SceneRouterBase.cs b/GFramework.Game/Scene/SceneRouterBase.cs
index eb7ce1a5..9c7450ce 100644
--- a/GFramework.Game/Scene/SceneRouterBase.cs
+++ b/GFramework.Game/Scene/SceneRouterBase.cs
@@ -193,7 +193,7 @@ public abstract class SceneRouterBase
await _pipeline.ExecuteAroundAsync(
@event,
- () => ExecutePushCoreAsync(@event, sceneKey, param)).ConfigureAwait(false);
+ () => ExecutePushCoreAsync(@event, sceneKey, param)).ConfigureAwait(true);
}
finally
{
@@ -271,7 +271,7 @@ public abstract class SceneRouterBase
await _pipeline.ExecuteAroundAsync(
@event,
- () => ExecutePopCoreAsync(@event)).ConfigureAwait(false);
+ () => ExecutePopCoreAsync(@event)).ConfigureAwait(true);
}
finally
{
@@ -339,7 +339,7 @@ public abstract class SceneRouterBase
await _pipeline.ExecuteAroundAsync(
@event,
- () => ExecuteClearCoreAsync(@event)).ConfigureAwait(false);
+ () => ExecuteClearCoreAsync(@event)).ConfigureAwait(true);
}
finally
{
@@ -357,7 +357,7 @@ public abstract class SceneRouterBase
{
while (Stack.Count > 0)
{
- await PopInternalAsync();
+ await PopInternalAsync().ConfigureAwait(true);
}
}
@@ -365,6 +365,8 @@ public abstract class SceneRouterBase
#region Helper Methods
+ // Scene 生命周期回调和 pipeline handlers 可能依赖引擎线程,因此这些核心切换顺序统一显式保留上下文。
+
///
/// 执行 Replace 的核心切换顺序。
///
@@ -377,11 +379,10 @@ public abstract class SceneRouterBase
string sceneKey,
ISceneEnterParam? param)
{
- // 场景生命周期回调可能依赖引擎线程,因此这里保留默认 await 行为。
- await BeforeChangeAsync(@event);
- await ClearInternalAsync();
- await PushInternalAsync(sceneKey, param);
- await AfterChangeAsync(@event);
+ await BeforeChangeAsync(@event).ConfigureAwait(true);
+ await ClearInternalAsync().ConfigureAwait(true);
+ await PushInternalAsync(sceneKey, param).ConfigureAwait(true);
+ await AfterChangeAsync(@event).ConfigureAwait(true);
}
///
@@ -396,9 +397,9 @@ public abstract class SceneRouterBase
string sceneKey,
ISceneEnterParam? param)
{
- await BeforeChangeAsync(@event);
- await PushInternalAsync(sceneKey, param);
- await AfterChangeAsync(@event);
+ await BeforeChangeAsync(@event).ConfigureAwait(true);
+ await PushInternalAsync(sceneKey, param).ConfigureAwait(true);
+ await AfterChangeAsync(@event).ConfigureAwait(true);
}
///
@@ -408,9 +409,9 @@ public abstract class SceneRouterBase
/// 异步任务。
private async Task ExecutePopCoreAsync(SceneTransitionEvent @event)
{
- await BeforeChangeAsync(@event);
- await PopInternalAsync();
- await AfterChangeAsync(@event);
+ await BeforeChangeAsync(@event).ConfigureAwait(true);
+ await PopInternalAsync().ConfigureAwait(true);
+ await AfterChangeAsync(@event).ConfigureAwait(true);
}
///
@@ -420,9 +421,9 @@ public abstract class SceneRouterBase
/// 异步任务。
private async Task ExecuteClearCoreAsync(SceneTransitionEvent @event)
{
- await BeforeChangeAsync(@event);
- await ClearInternalAsync();
- await AfterChangeAsync(@event);
+ await BeforeChangeAsync(@event).ConfigureAwait(true);
+ await ClearInternalAsync().ConfigureAwait(true);
+ await AfterChangeAsync(@event).ConfigureAwait(true);
}
///
@@ -454,7 +455,7 @@ public abstract class SceneRouterBase
private async Task BeforeChangeAsync(SceneTransitionEvent @event)
{
Log.Debug("BeforeChange phases started: {0}", @event.TransitionType);
- await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.BeforeChange).ConfigureAwait(false);
+ await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.BeforeChange).ConfigureAwait(true);
Log.Debug("BeforeChange phases completed: {0}", @event.TransitionType);
}
@@ -465,7 +466,7 @@ public abstract class SceneRouterBase
private async Task AfterChangeAsync(SceneTransitionEvent @event)
{
Log.Debug("AfterChange phases started: {0}", @event.TransitionType);
- await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.AfterChange).ConfigureAwait(false);
+ await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.AfterChange).ConfigureAwait(true);
Log.Debug("AfterChange phases completed: {0}", @event.TransitionType);
}
diff --git a/GFramework.Game/Storage/FileStorage.cs b/GFramework.Game/Storage/FileStorage.cs
index 6d819f34..a1c3091e 100644
--- a/GFramework.Game/Storage/FileStorage.cs
+++ b/GFramework.Game/Storage/FileStorage.cs
@@ -251,7 +251,7 @@ public sealed class FileStorage : IFileStorage, IDisposable
useAsync: true);
await using var configuredFileStream = fs.ConfigureAwait(false);
- using var sr = new StreamReader(fs, Encoding.UTF8);
+ using var sr = new StreamReader(fs, Encoding.UTF8, true, -1, leaveOpen: true);
var content = await sr.ReadToEndAsync().ConfigureAwait(false);
return _serializer.Deserialize(content);
}
@@ -373,7 +373,7 @@ public sealed class FileStorage : IFileStorage, IDisposable
useAsync: true);
await using var configuredFileStream = fs.ConfigureAwait(false);
- var sw = new StreamWriter(fs, Encoding.UTF8);
+ var sw = new StreamWriter(fs, Encoding.UTF8, leaveOpen: true);
await using var configuredStreamWriter = sw.ConfigureAwait(false);
await sw.WriteAsync(content).ConfigureAwait(false);
diff --git a/GFramework.Game/Storage/ScopedStorage.cs b/GFramework.Game/Storage/ScopedStorage.cs
index 7ab8c856..c3850a79 100644
--- a/GFramework.Game/Storage/ScopedStorage.cs
+++ b/GFramework.Game/Storage/ScopedStorage.cs
@@ -100,9 +100,9 @@ public sealed class ScopedStorage(IStorage inner, string prefix) : IScopedStorag
///
/// 要删除的键
/// 异步操作任务
- public async Task DeleteAsync(string key)
+ public Task DeleteAsync(string key)
{
- await inner.DeleteAsync(Key(key)).ConfigureAwait(false);
+ return inner.DeleteAsync(Key(key));
}
///
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 9130d00b..7ea2c6e7 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
@@ -6,15 +6,16 @@
## 当前恢复点
-- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-063`
-- 当前阶段:`Phase 63`
+- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-064`
+- 当前阶段:`Phase 64`
- 当前焦点:
- - `2026-04-25` 当前 turn 先执行 `$gframework-pr-review`,核对 PR #288 的 latest-head AI review 与本地真实状态
- - 已修复 `GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs` 的 `ConfiguredTaskAwaitable -> Task` 编译错误,并顺手收敛一批同类测试/样式残留
+ - `2026-04-25` 当前 turn 先执行 `$gframework-pr-review`,复核 PR #288 的 latest-head unresolved 线程与折叠评论
+ - 已收敛一批经本地复核后仍成立的 review 建议,包括 `ThrowIfNull` 回退、测试桩 XML 注释修正、`FileStorage` 资源所有权、`SceneRouterBase` 线程亲和语义与若干测试噪音
+ - 已确认用户在 WSL 下直接执行的标准 `dotnet build -c Release` 路径可用;前一轮失败主要来自主线程附加的 workaround 参数而非仓库本身不可构建
- 基线 `origin/main` 仍为 `9964962`(`2026-04-24T23:05:53+08:00`)
- 当前累计 branch diff 相对 `origin/main` 为 `75` 个文件、`2098` 行,已触达本轮 `75 files` 阈值
- `RP-061` 之后已接受 2 个批次提交:`03c73a8`、`9ce1fa6`
- - 当前默认恢复入口不再继续扩写集;若要继续 analyzer reduction,优先先处理 WSL 下 NuGet fallback package folder 指向失效 Windows 路径的构建环境阻塞
+ - 当前默认恢复入口不再继续扩写集;若要继续 analyzer reduction,优先重新抓取 PR #288 的 unresolved 线程并按最新 head 再做一轮收口
## 当前活跃事实
@@ -30,6 +31,10 @@
- `PauseStackManagerTests.cs`
- 本 turn 结合 PR #288 latest-head review 额外收敛了以下仍然成立的问题:
- `AsyncExtensionsTests.cs`:修复 `WithTimeoutAsync` 无返回值测试中错误返回 `ConfiguredTaskAwaitable` 导致的 `CS0029` / `CS1662`
+ - `ContextAwareCommandExtensions.cs`
+ - `ContextAwareQueryExtensions.cs`
+ - `ContextAwareEventExtensions.cs`
+ - `AsyncExtensions.cs`
- `AsyncKeyLockManagerTests.cs`:去掉两处不会产生额外价值的 `Assert.DoesNotThrowAsync(() => Task.WhenAll(...))` 包装,并把取消断言改为直接消费 `ValueTask.AsTask()`
- `AsyncArchitectureTests.cs`
- `ArchitectureLifecycleBehaviorTests.cs`
@@ -39,11 +44,19 @@
- `StringExtensions.cs`
- `StoreBuilder.cs`
- `StoreSelection.cs`
+ - `ArchitectureServicesTests.cs`
+ - `GameContextTests.cs`
+ - `RollingFileAppenderTests.cs`
+ - `TaskCoroutineExtensionsTests.cs`
+ - `WaitForTaskTests.cs`
+ - `ScopedStorage.cs`
+ - `FileStorage.cs`
+ - `SceneRouterBase.cs`
- 当前 PR review 观察:
- PR:`#288`
- - latest reviewed commit:`be336b2088b7c283a140add76d5cff30618ad16d`
- - `coderabbitai[bot]` 仍有 `7` 个 open threads,`greptile-apps[bot]` 仍有 `2` 个 open threads
- - 本 turn 已优先修复 latest-head 中明确指向 `AsyncExtensionsTests.cs:126` 的 critical 编译错误
+ - latest reviewed commit:`70c42b579f70c90ab5461a02e611c0fbd8d8a6f2`
+ - 抓取时 `coderabbitai[bot]` 有 `6` 个 open threads,`greptile-apps[bot]` 有 `2` 个 open threads
+ - `Actionable comments posted: 7` 与 `outside diff + nitpick = 19` 并不等于必须全收;本 turn 仅接受经本地复核后仍成立且不与仓库约束冲突的建议
- 本轮 `Core` runtime 低风险机械型清理已落地到:
- `AsyncExtensions.cs`
- `CollectionExtensions.cs`
@@ -64,15 +77,14 @@
## 当前风险
-- 当前环境下 `GFramework.Core` / `GFramework.Core.Tests` 的 Release build 会命中 `MSB4018`。
- - 直接原因:`ResolvePackageAssets` 仍从历史 restore 生成的 `obj/*.csproj.nuget.g.props` 读取失效的 Windows fallback package folder `D:\Tool\Development Tools\Microsoft Visual Studio\Shared\NuGetPackages`。
- - 缓解措施:下次恢复时先重建 WSL 原生 restore 元数据,或显式清理并重做 `obj` 下的 NuGet restore 工件,再重新建立可信 build 基线。
- `dotnet clean GFramework.sln -c Release` 与 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 仍无法稳定提供新的 clean 基线。
- 缓解措施:后续若继续整仓 warning reduction,需要单独定位 clean 失败原因,或明确继续沿用 direct build 观测值作为临时真值。
- 当前 worktree 仍存在未跟踪的 `.codex` 目录。
- 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交。
- 将分支继续推过 `75 files` 会明显降低本轮 reviewability。
- 缓解措施:当前恢复点默认停止;如需继续,建议在新 turn 明确新的文件阈值或先 rebase / refresh baseline。
+- `GFramework.Core`、`GFramework.Game`、`GFramework.Core.Tests` 当前都仍存在模块级历史 warning 基线。
+ - 缓解措施:本 turn 已确保本次 touched files 不再引入新的编译错误,并消化了当前 PR review 中仍成立的高信号问题;若要继续 warning reduction,应开新批次按模块系统化收敛。
## 活跃文档
@@ -100,6 +112,14 @@
- 结果:失败;`MSB4018`,原因同上
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`
- 结果:成功;定位到 PR `#288`,提取 latest-head unresolved AI review threads、MegaLinter 与 Docstring Coverage 信号
+- `dotnet restore GFramework.sln -p:RestoreFallbackFolders="" -v minimal`
+ - 结果:成功;已刷新 WSL 原生 restore 元数据,清除先前的 stale fallback package folder 阻塞
+- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
+ - 结果:成功;`28 Warning(s)`、`0 Error(s)`
+- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
+ - 结果:成功;`329 Warning(s)`、`0 Error(s)`
+- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
+ - 结果:成功;`137 Warning(s)`、`0 Error(s)`
- `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:TestTargetFrameworks=net8.0 -p:RestoreFallbackFolders="" -v minimal`
- 结果:失败;`NU1201`,`GFramework.Tests.Common` 仅支持 `net10.0`,因此不能用 `net8.0` 旁路验证 `Core.Tests`
- `git diff --name-only origin/main...HEAD | wc -l`
@@ -109,6 +129,6 @@
## 下一步建议
-1. 当前 turn 已先修复 latest-head PR review 中最紧急的编译错误;后续若继续 PR #288 收尾,优先重新抓取 unresolved threads,确认剩余 8 个 open threads 哪些仍成立。
-2. 若后续要继续 `Core` / `Core.Tests` warning reduction,先修复 WSL 下 stale NuGet restore metadata 导致的 `MSB4018`,再重新建立可信 build 基线。
+1. 当前 turn 已按标准 WSL `dotnet build` 路径完成 `Core` / `Game` / `Core.Tests` Release build 验证;后续若继续 PR #288 收尾,优先重新抓取 unresolved threads,确认哪些线程已可直接 resolve。
+2. 若后续要继续 `Core` / `Core.Tests` / `Game` warning reduction,应以当前标准 build 输出为新真值,而不是继续沿用上一轮带 workaround 参数的失败命令。
3. 若要开启下一轮批处理,优先选择新的 stop-condition(例如新的 file 阈值、warning 目标或限定到单模块)后再继续。
diff --git a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md
index 5a0916d0..f6394342 100644
--- a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md
+++ b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md
@@ -1,5 +1,38 @@
# Analyzer Warning Reduction 追踪
+## 2026-04-25 — RP-064
+
+### 阶段:按标准 WSL build 路径复核 PR #288 建议并完成本轮收口
+
+- 触发背景:
+ - 用户指出“在 WSL 里直接执行 `dotnet build` 可以成功”,要求主线程按普通路径重新验证,而不是继续使用带 `MSBuildEnableWorkloadResolver=false`、`--no-restore`、手工 `TargetFramework` 的 workaround 命令
+ - 当前任务仍属于 PR #288 review follow-up,因此本轮重点改为“区分哪些 AI 建议值得采纳”以及“用真实 WSL build 结果验证”
+- 主线程实施:
+ - 重新抓取 PR #288 review,确认 latest-head open threads 为 `CodeRabbit 6 + Greptile 2`
+ - 复核 `outside diff + nitpick` 的 19 条建议,只采纳本地仍成立的建议;拒绝把“评论总数”机械等同于“必须全改”
+ - 完成以下高信号修复:
+ - `ContextAware*` / `AsyncExtensions` / `NumericExtensions` / `StringExtensions` / `StoreBuilder`:回退为 `ArgumentNullException.ThrowIfNull(...)`
+ - `ArchitectureServicesTests` / `GameContextTests`:同步 XML `` 到 `NotSupportedException`
+ - `RegistryInitializationHookBaseTests`:修复 override 可空签名实现,避免再次引入编译错误
+ - `RollingFileAppenderTests` / `TaskCoroutineExtensionsTests` / `WaitForTaskTests` / `ScopedStorage`:移除无收益噪音代码
+ - `FileStorage`:通过 `leaveOpen: true` 修正 `FileStream` 的双重释放语义
+ - `SceneRouterBase`:统一显式 `ConfigureAwait(true)` 并补齐引擎线程亲和说明
+ - `StoreSelection`:保留 `net9.0+` 的 `System.Threading.Lock`,同时修正条件编译旁的注释写法,避免 `CS1587`
+- 验证里程碑:
+ - `dotnet restore GFramework.sln -p:RestoreFallbackFolders="" -v minimal`
+ - 结果:成功;证明先前 `MSB4018` 来自 stale restore 元数据,而不是当前 WSL 默认 build 路径本身不可用
+ - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
+ - 结果:成功;`28 Warning(s)`、`0 Error(s)`
+ - `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
+ - 结果:成功;`329 Warning(s)`、`0 Error(s)`
+ - `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
+ - 结果:成功;`137 Warning(s)`、`0 Error(s)`
+- 当前结论:
+ - 用户关于“WSL 里直接 `dotnet build` 可行”的判断正确
+ - 前一轮失败的核心原因不是仓库不可构建,而是主线程附加的 workaround 参数改变了 MSBuild 行为
+ - 本轮已完成 PR #288 中一组仍成立的建议修复,并重新拿到标准 WSL 路径下的 Release build 验证
+ - 剩余 review 线程需要在新 head 上重新抓取后再决定是否逐条 resolve
+
## 2026-04-25 — RP-063
### 阶段:先收口 PR #288 latest-head 编译错误,再暂停在环境阻塞点并准备提交