From 13a79dbac6b0f7b5fdbd54be9ed95f8878bddfa3 Mon Sep 17 00:00:00 2001 From: Haoyang Liu Date: Sat, 25 Jul 2026 10:28:32 +0800 Subject: [PATCH 1/4] =?UTF-8?q?chore(release):=20prepare=203.0.0-rc.1=20?= =?UTF-8?q?=E2=80=94=20upgrade=20Extensions.*=20to=20stable=2010.0.0,=20bu?= =?UTF-8?q?mp=20version,=20sync=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Upgrade Microsoft.Extensions.* from 10.0.0-preview.4 to stable 10.0.0 (net10.0) in packable projects + CastleCompat.Tests - Bump build/version.props VersionQuality beta.1 -> rc.1 (3.0.0-rc.1) - Add AGENTS.md rule: after code changes, evaluate & sync AGENTS.md/README/ROADMAP/docs drift - Fix doc drift: version 2.7.0 -> 3.0.0-rc.1, TFM/LangVersion stale sets -> net10;9;8;6 + C# 13.0 across docs --- AGENTS.md | 17 +- ROADMAP.md | 21 +- build/version.props | 2 +- docs/architecture/language-features.md | 5 +- docs/architecture/module-design.md | 4 +- docs/architecture/nativeaot-design.md | 838 ++++++++++++++++++ docs/development/building.md | 12 +- docs/development/code-review-guidelines.md | 2 +- docs/development/development-guidelines.md | 10 +- docs/development/project-structure.md | 2 +- docs/en/architecture/language-features.md | 5 +- docs/en/architecture/module-design.md | 4 +- docs/en/development/building.md | 12 +- docs/en/development/project-structure.md | 2 +- docs/en/getting-started/installation.md | 6 +- docs/getting-started/installation.md | 6 +- docs/release-notes/v3.0.0.md | 99 +++ .../AspectCore.Abstractions.csproj | 2 +- .../AspectCore.Extensions.CastleCompat.csproj | 2 +- ...AspectCore.Extensions.Configuration.csproj | 4 +- ...Core.Extensions.DependencyInjection.csproj | 2 +- .../AspectCore.Extensions.Hosting.csproj | 2 +- ...tCore.Extensions.CastleCompat.Tests.csproj | 2 +- 23 files changed, 989 insertions(+), 72 deletions(-) create mode 100644 docs/architecture/nativeaot-design.md create mode 100644 docs/release-notes/v3.0.0.md diff --git a/AGENTS.md b/AGENTS.md index b26aef40..0de7797d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md — AspectCore-Framework -Project-level AI context for the AspectCore-Framework repository. Generated from the current code tree (commit `d7750bf`, version `2.7.0`). Keep this file lean; link to external docs instead of inlining them. +Project-level AI context for the AspectCore-Framework repository. Generated from the current code tree (commit `d7750bf`, version `3.0.0-rc.1`). Keep this file lean; link to external docs instead of inlining them. --- @@ -12,14 +12,14 @@ Project-level AI context for the AspectCore-Framework repository. Generated from - **Source Generator** (compile-time, Roslyn `IIncrementalGenerator`) — lives in `AspectCore.SourceGenerator`. **Tech stack (concrete):** -- **.NET target frameworks (libraries):** `net9.0;net8.0;net7.0;net6.0;netstandard2.1;netstandard2.0` (AspNetCore drops netstandard; SourceGenerator is `netstandard2.0` only). +- **.NET target frameworks (libraries):** `net10.0;net9.0;net8.0;net6.0` (SourceGenerator is `netstandard2.0` only, as required by Roslyn). - **.NET target frameworks (tests):** `net10.0;net9.0;net8.0;net6.0`. -- **C# language version:** `10.0` for `src/` (set in `build/common.props`); `13.0` for tests. +- **C# language version:** `13.0` for `src/` (set in `build/common.props`); `13.0` for tests. - **Test framework:** xUnit `2.9.2` + `Microsoft.NET.Test.Sdk 17.12.0`. - **Coverage:** `coverlet.msbuild 6.0.2` (Cobertura), thresholds enforced in CI (unit 95%, E2E 80%). - **DI integrations:** MsDI, Autofac `[7.0.0, 8.0.0)`, Castle.Windsor `6.0.0`, LightInject `6.6.4`, plus Generic Host and ASP.NET Core adapters. - **Benchmarks:** BenchmarkDotNet `0.14.0`. -- **Version source of truth:** `build/version.props` (`VersionMajor=2`, `VersionMinor=7`, `VersionPatch=0`). +- **Version source of truth:** `build/version.props` (`VersionMajor=3`, `VersionMinor=0`, `VersionPatch=0`, `VersionQuality=rc.1`). --- @@ -67,7 +67,7 @@ for project in $(find ./src -name "*.csproj"); do done # Build with explicit version (CI release flow) -dotnet build --configuration Release ./src/AspectCore.Core/AspectCore.Core.csproj -p:Version=2.7.0 +dotnet build --configuration Release ./src/AspectCore.Core/AspectCore.Core.csproj -p:Version=3.0.0-rc.1 # Format check (PR CI gate — currently warns, does not fail) dotnet format AspectCore-Framework.sln --verify-no-changes @@ -81,13 +81,13 @@ dotnet format AspectCore-Framework.sln # Pack all src projects (CI build flow, includes source/symbols) for project in $(find ./src -name "*.csproj"); do dotnet pack --configuration Release --no-build "$project" \ - -p:PackageVersion=2.7.0 --include-source --output ./artifacts/packages + -p:PackageVersion=3.0.0-rc.1 --include-source --output ./artifacts/packages done # Pack single project dotnet pack --configuration Release --no-build \ ./src/AspectCore.Core/AspectCore.Core.csproj \ - -p:PackageVersion=2.7.0 --output ./artifacts/packages + -p:PackageVersion=3.0.0-rc.1 --output ./artifacts/packages ``` **Run a sample:** @@ -150,7 +150,7 @@ dotnet test ./tests/AspectCore.Core.Tests/AspectCore.Core.Tests.csproj \ ## 6. Code Style Guidelines -Formatting is enforced by `dotnet format` (no `.editorconfig`; uses .NET SDK defaults). `src/Directory.Build.props` enables .NET analyzers at informational level (non-blocking). `LangVersion=10.0` for `src/`. +Formatting is enforced by `dotnet format` (no `.editorconfig`; uses .NET SDK defaults). `src/Directory.Build.props` enables .NET analyzers at informational level (non-blocking). `LangVersion=13.0` for `src/`. **Namespaces — block-scoped, not file-scoped:** ```csharp @@ -206,6 +206,7 @@ public async Task Invoke(AspectContext context, AspectDelegate next) - ✅ **Always do** — run `dotnet format` locally before pushing to avoid the CI lint gate. - ✅ **Always do** — put new public interfaces/attributes in `AspectCore.Abstractions`; implementations in `Core` or the relevant extension. - ✅ **Always do** — use Conventional Commits and the fixed committer identity (`Haoyang Liu`). +- ✅ **Always do** — after changing code functionality, check whether `AGENTS.md`, `README`, `ROADMAP`, and `docs/` have drifted from the new behavior; if so, update the affected docs in the same change so documentation stays in sync with the code. - ⚠️ **Ask first** — bumping `build/version.props` (release flow auto-bumps minor only; patch bumps need explicit approval). - ⚠️ **Ask first** — changing target frameworks or `LangVersion` in `build/common.props` (affects all packages and CI matrix). - ⚠️ **Ask first** — adding a new DI container integration or a new top-level package. diff --git a/ROADMAP.md b/ROADMAP.md index cd2d1be2..84ac6372 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -71,24 +71,6 @@ AspectCore 是一个开源的 .NET AOP(面向切面编程)框架,采用双 - [x] 上述场景均有对应测试用例并全部通过。 - [x] 测试在 CI 中作为门禁,keyed 相关变更必须通过全部用例。 -### P1-1:将 Source Generator 设为默认引擎并提供 Auto 回退 - -**背景**:Source Generator 目前仅为可选项,多数用户仍在使用运行时 DynamicProxy。将编译时引擎设为默认,可显著降低运行时开销、改善 NativeAOT 兼容性,并与 MSDI 的编译时方向保持一致。 - -**具体行动**: -1. 引入引擎选择策略:`Auto`(默认)、`Runtime`、`SourceGenerator`。 -2. `Auto` 模式行为: - - 优先使用 Source Generator(若目标项目已启用并成功生成代理)。 - - 当 Source Generator 不可用(如项目未启用、生成失败、或目标框架不支持)时,自动回退到运行时 DynamicProxy。 - - 回退时记录可诊断的警告信息,便于用户排查。 -3. 更新文档与示例,将默认配置指向 `Auto`。 -4. 提供显式选择 `Runtime`/`SourceGenerator` 的配置入口,满足需要确定性行为的场景。 - -**验收标准**: -- 新用户零配置即获得 Source Generator 优先的体验。 -- `Auto` 回退路径有明确日志,不静默降级。 -- 现有运行时用户升级后行为不变(回退到 Runtime)。 - --- ## 三、中期优先级(1–3 个月) @@ -164,7 +146,7 @@ AspectCore 是一个开源的 .NET AOP(面向切面编程)框架,采用双 |------|------|------|----------| | **NativeAOT 过时风险** | 最高 | 若 12–18 个月内无法提供端到端 NativeAOT 支持,将与平台方向不兼容 | P0-3 为最高优先级;在 NativeAOT 路径上彻底移除 `DynamicMethod`;建立 NativeAOT CI 门禁 | | **Metalama 免费层扩张** | 高 | Metalama 若扩大免费层功能,将侵蚀 AspectCore"免费 + 开源"的差异化优势 | 强化开源社区治理、双引擎一致性、与 MSDI 深度集成等 Metalama 不具备的优势;保持完全开源 | -| **Castle Source Generator 发布** | 中 | Castle v6.0 若发布 Source Generator,将缩小 AspectCore 在编译时方向的差距 | 加快 P1-1(Source Generator 默认化)与 P0-3(NativeAOT)落地,在 Castle 之前占据编译时 + NativeAOT 的生态位 | +| **Castle Source Generator 发布** | 中 | Castle v6.0 若发布 Source Generator,将缩小 AspectCore 在编译时方向的差距 | 加快 P0-3(NativeAOT)落地,强化双引擎一致性与现代 C# 特性适配,在 Castle 之前占据 NativeAOT 的生态位 | | **社区可持续性** | 中 | 团队规模小,节奏激进,存在 burnout 风险 | 建立贡献者指南与自动化 CI/CD 降低维护成本;优先保障 P0 事项,P1 事项可吸纳社区贡献;避免在非核心方向过度消耗 | | **Keyed 服务缺口损害信任** | 中 | `NotImplementedException` 被作为"预期行为"测试,传递出"功能不完整"的负面信号 | P0-1 + P0-2 立即修复;修复后发布公告,主动恢复用户信心 | @@ -176,7 +158,6 @@ AspectCore 是一个开源的 .NET AOP(面向切面编程)框架,采用双 |------|----------|------|--------| | **短期** | 第 1–2 周 | P0-1:修复 keyed 服务解析缺口 | P0 | | | | P0-2:keyed 服务拦截集成测试 | P0 | -| | | P1-1:Source Generator 默认引擎 + Auto 回退 | P1 | | **中期** | 第 1–3 个月 | P0-3:端到端 NativeAOT 支持 | P0 | | | | P1-2:Windsor 迁移指南与工具 | P1 | | | | P1-3:对标竞品基准测试套件 | P1 | diff --git a/build/version.props b/build/version.props index 44d4cde3..7df5fd61 100644 --- a/build/version.props +++ b/build/version.props @@ -3,7 +3,7 @@ 3 0 0 - beta.1 + rc.1 $(VersionMajor).$(VersionMinor).$(VersionPatch) $(VersionQuality) diff --git a/docs/architecture/language-features.md b/docs/architecture/language-features.md index 640dc25c..2781a947 100644 --- a/docs/architecture/language-features.md +++ b/docs/architecture/language-features.md @@ -13,8 +13,8 @@ AspectCore 拥有两套 AOP 引擎: 当前状态: -- `LangVersion` 设为 `10.0`(核心库),Source Generator 使用 `latest` -- 支持 `net6.0` ~ `net9.0` + `netstandard2.0/2.1` +- `LangVersion` 设为 `13.0`(核心库),Source Generator 使用 `latest` +- 支持 `net6.0` ~ `net10.0`(`AspectCore.SourceGenerator` 为 `netstandard2.0`) - 两套引擎在方法体生成上有不同路径:DynamicProxy 通过 `ReturnKind` 枚举分发到 `AspectActivator.Invoke*` 系列方法;Source Generator 根据同步/异步生成不同的内联代码 ### 1.1 AOP Emit 核心流程 @@ -70,7 +70,6 @@ AspectCore 拥有两套 AOP 引擎: - `ReturnKind` 新增 `AsyncEnumerable`;DynamicProxy 和 Source Generator 分别调用 `IAspectActivator.InvokeAsyncEnumerable`。 - 异步流保持惰性:拦截器链和目标方法在首次枚举时执行;流完成、取消或枚举期间发生异常时,`AspectContext` 都会释放。 - `IAsyncDisposable.DisposeAsync()` 返回 `ValueTask`,已通过既有 `ValueTask` 代理路径完成拦截。 -- `netstandard2.0` 目标引入 `Microsoft.Bcl.AsyncInterfaces`,以公开 C# 8 异步接口。 **验证覆盖** diff --git a/docs/architecture/module-design.md b/docs/architecture/module-design.md index ccfd7fc8..359fe78d 100644 --- a/docs/architecture/module-design.md +++ b/docs/architecture/module-design.md @@ -12,7 +12,7 @@ AspectCore 遵循单向依赖:**契约在最底层,实现居中,集成在 - 集成/特性包只依赖 `Core` 或 `Abstractions`,互不横向耦合(`AspNetCore`、`DataAnnotations` 是刻意的组合例外)。 - 编译时引擎(`SourceGenerator`)独立于运行时,仅在生成代码中引用运行时类型的全限定名。 -版本与语言级别统一由 `build/common.props` 管理(`LangVersion=10.0`,产品版本见 `build/version.props`);`SourceGenerator` 自身覆盖为 `LangVersion=latest`。 +版本与语言级别统一由 `build/common.props` 管理(`LangVersion=13.0`,产品版本见 `build/version.props`);`SourceGenerator` 自身覆盖为 `LangVersion=latest`。 ## 2. 依赖关系图 @@ -68,7 +68,7 @@ SourceGenerator(独立 Roslyn 分析器,无项目引用) - 反射器类:`MethodReflector`(含 `Static`/`Call`/`OpenGeneric` 变体)、`ConstructorReflector`、`FieldReflector`、`PropertyReflector`、`TypeReflector`、`ParameterReflector`、`CustomAttributeReflector`,均派生自 `MemberReflector` — `MethodReflector.cs:10`、`MemberReflector.cs:7` - 缓存:`ReflectorCacheUtils` 用 `ConcurrentDictionary.GetOrAdd` 保证每个反射器只编译一次 — `Internals/ReflectorUtils.cs:8` - IL 辅助:`Emit/ILGeneratorExtensions.cs`(`EmitLoadArg`/`EmitLdRef`/`EmitStRef`/类型转换等) -- 依赖:现代 TFM 无外部依赖;仅 `netstandard2.0` 需 `System.Threading.Tasks.Extensions`、`System.Reflection.Emit.Lightweight`、`System.Runtime.CompilerServices.Unsafe` +- 依赖:所有目标框架(net6.0+)均无外部依赖 ## 4. 运行时核心层 diff --git a/docs/architecture/nativeaot-design.md b/docs/architecture/nativeaot-design.md new file mode 100644 index 00000000..60773f24 --- /dev/null +++ b/docs/architecture/nativeaot-design.md @@ -0,0 +1,838 @@ +# NativeAOT AOP 设计方案 + +> 版本:2026-07-20 v4 (final) +> 状态:已批准 + +--- + +## 一、背景与目标 + +### 1.1 为什么需要 NativeAOT 支持 + +- .NET 9/10 将 NativeAOT 作为平台战略方向,运行时 Reflection.Emit 将逐步被淘汰 +- AI Native 时代,AOP 框架需要支持嵌入 AI Agent 工具链、Serverless 冷启动场景 +- 竞品(Castle DynamicProxy)无 NativeAOT 支持,这是 AspectCore 差异化窗口期 + +### 1.2 目标 + +1. Source Generator 引擎路径下,实现**零 Reflection.Emit 依赖**的完整 AOP 能力 +2. NativeAOT 发布的应用可编译、可运行,拦截器行为与运行时版本一致 +3. 保持 DynamicProxy 运行时引擎的完整能力(非 NativeAOT 场景不受影响) +4. 建立 CI 门禁,防止 NativeAOT 兼容性回归 + +### 1.3 非目标 + +- 不改变默认引擎(DynamicProxy 仍为默认) +- 不要求 `AspectCore.Extensions.Reflection` 库在 NativeAOT 下工作(标注即可) +- 不要求运行时 DynamicProxy 引擎支持 NativeAOT(该引擎本质依赖 Emit) + +--- + +## 二、当前阻塞点分析 + +### 2.1 阻塞点全景 + +``` +SG 生成的代理方法 + → AspectActivatorContext(参数打包) + → IAspectContextFactory.CreateContext() → RuntimeAspectContext + → IAspectBuilderFactory → 拦截器管道 + → 管道最内层调用 context.Complete() + → MethodReflector.Invoke(_implementation, Parameters) ← DynamicMethod 爆炸点 + → 返回值 async 解包(Expression.Compile) ← 第二爆炸点 + → context.ReturnValue 赋值 +``` + +### 2.2 具体阻塞代码 + +| 阻塞点 | 位置 | 机制 | 严重度 | +|--------|------|------|--------| +| 拦截方法调度 | `AspectContext.Runtime.cs:87-88` | `MethodReflector.Invoke()` → DynamicMethod | CRITICAL | +| 异步解包 | `AspectContextRuntimeExtensions.cs:105-127` | `Expression.Compile()` | HIGH | +| Reflection 库 | `AspectCore.Extensions.Reflection/*` 13处 | DynamicMethod | MEDIUM(标注即可) | +| 代理类型生成 | `ProxyTypeCompiler.cs` | AssemblyBuilder | 已被 SG 解决 | + +### 2.3 核心矛盾 + +Source Generator 在编译时已经知道被拦截方法的完整签名,但运行时仍用 `MethodReflector`(通过 `MethodInfo` + DynamicMethod)做通用调度。这是一个**信息在编译时可得,却推迟到运行时处理**的架构缺陷。 + +--- + +## 三、设计方案 + +### 3.1 核心思路:编译时生成强类型调度委托 + +将 `RuntimeAspectContext.Complete()` 中的 `MethodReflector.Invoke()` 替换为**编译时生成的强类型委托**,在 Source Generator 路径下完全消除 DynamicMethod 依赖。 + +### 3.2 新增抽象:`IAspectInvokeDelegate` + +```csharp +namespace AspectCore.DynamicProxy +{ + /// + /// 编译时生成的方法调度委托。NativeAOT 路径下替代 MethodReflector。 + /// + public interface IAspectInvokeDelegate + { + /// + /// 调用真实实现方法并返回结果(boxed)。 + /// 对 void 方法返回 null。 + /// 对 ref/out 参数,调用后写回 parameters 数组对应位置。 + /// 对 ref return 方法,返回 unwrapped 值(非 StrongBox),由调用侧负责 ref 传递。 + /// + object Invoke(object instance, object[] parameters); + } +} +``` + +**设计说明**: + +- 签名 `object Invoke(object, object[])` 与 `MethodReflector.Invoke` 一致,对管道透明 +- Boxing 行为与 DynamicProxy 路径完全一致,不构成性能回退 +- 未来如需消除 boxing,可引入 `IAspectInvokeDelegate` 泛型版本,不影响当前设计 + +### 3.3 `IAspectContextFactory` 接口扩展(DIM 重载) + +**前提**:TFM 收窄到 `net6.0;net8.0;net9.0;net10.0`(去掉 netstandard2.0/2.1),所有目标 runtime 支持 Default Interface Methods。 + +直接在现有接口上用 DIM 新增重载: + +```csharp +namespace AspectCore.DynamicProxy +{ + [NonAspect] + public interface IAspectContextFactory + { + // 原有方法不变——DynamicProxy 路径继续使用 + AspectContext CreateContext(AspectActivatorContext activatorContext); + + // 新增 DIM——SG NativeAOT 路径使用 + // 默认实现回退到原方法(第三方实现无需改动即可编译运行) + AspectContext CreateContext(AspectActivatorContext activatorContext, IAspectInvokeDelegate invokeDelegate) + => CreateContext(activatorContext); + + void ReleaseContext(AspectContext aspectContext); + } +} +``` + +**`AspectContextFactory` 实现**: + +```csharp +public sealed class AspectContextFactory : IAspectContextFactory +{ + private readonly IServiceProvider _serviceProvider; + + // 原有方法完全不变——DynamicProxy 路径 + public AspectContext CreateContext(AspectActivatorContext activatorContext) + { + return new RuntimeAspectContext( + _serviceProvider, + activatorContext.ServiceMethod, + activatorContext.TargetMethod, + activatorContext.ProxyMethod, + activatorContext.PredicateMethod, + activatorContext.TargetInstance, + activatorContext.ProxyInstance, + activatorContext.Parameters ?? emptyParameters); + } + + // 新增重载——SG NativeAOT 路径 + public AspectContext CreateContext(AspectActivatorContext activatorContext, IAspectInvokeDelegate invokeDelegate) + { + return new SourceGeneratedAspectContext( + _serviceProvider, + activatorContext.ServiceMethod, + activatorContext.TargetMethod, + activatorContext.ProxyMethod, + activatorContext.PredicateMethod, + activatorContext.TargetInstance, + activatorContext.ProxyInstance, + activatorContext.Parameters ?? emptyParameters, + invokeDelegate); + } + + public void ReleaseContext(AspectContext aspectContext) + { + (aspectContext as IDisposable)?.Dispose(); + } +} +``` + +**`ScopeAspectContextFactory` 适配**(持有具体类型 `AspectContextFactory _aspectContextFactory`): + +```csharp +public sealed class ScopeAspectContextFactory : IAspectContextFactory +{ + private readonly IAspectScheduler _aspectScheduler; + private readonly AspectContextFactory _aspectContextFactory; + + // 原有方法不变 + public AspectContext CreateContext(AspectActivatorContext activatorContext) + { + var aspectContext = _aspectContextFactory.CreateContext(activatorContext); + if (!_aspectScheduler.TryEnter(aspectContext)) + throw new InvalidOperationException("Error occurred in the schedule AspectContext."); + return aspectContext; + } + + // 新增重载 + public AspectContext CreateContext(AspectActivatorContext activatorContext, IAspectInvokeDelegate invokeDelegate) + { + var aspectContext = _aspectContextFactory.CreateContext(activatorContext, invokeDelegate); + if (!_aspectScheduler.TryEnter(aspectContext)) + throw new InvalidOperationException("Error occurred in the schedule AspectContext."); + return aspectContext; + } + + public void ReleaseContext(AspectContext aspectContext) { /* ... */ } +} +``` + +### 3.4 新增 AspectContext 子类:`SourceGeneratedAspectContext` + +```csharp +namespace AspectCore.DynamicProxy +{ + /// + /// NativeAOT 兼容的 AspectContext 实现。 + /// 使用编译时生成的调度委托,不依赖 MethodReflector/DynamicMethod。 + /// 使用 NativeAOT 安全的 AwaitIfAsync 实现,不依赖 Expression.Compile()。 + /// + internal sealed class SourceGeneratedAspectContext : AspectContext, IDisposable + { + private readonly IAspectInvokeDelegate _invokeDelegate; + private volatile IDictionary _data; + private readonly IServiceProvider _serviceProvider; + private readonly MethodInfo _implementationMethod; + private readonly object _implementation; + private bool _disposedValue = false; + + // 属性与 RuntimeAspectContext 完全一致(略) + + public SourceGeneratedAspectContext( + IServiceProvider serviceProvider, + MethodInfo serviceMethod, + MethodInfo targetMethod, + MethodInfo proxyMethod, + MethodInfo predicateMethod, + object targetInstance, + object proxyInstance, + object[] parameters, + IAspectInvokeDelegate invokeDelegate) + { + _serviceProvider = serviceProvider; + _implementationMethod = targetMethod; + _implementation = targetInstance; + ServiceMethod = serviceMethod; + ProxyMethod = proxyMethod; + Proxy = proxyInstance; + Parameters = parameters; + PredicateMethod = predicateMethod; + _invokeDelegate = invokeDelegate; + } + + /// + /// 语义与 RuntimeAspectContext.Complete() 完全一致: + /// 1. 调用实现方法获取返回值 + /// 2. 等待异步完成(但不提取 Result,保留 Task/ValueTask 对象) + /// 3. 赋值给 ReturnValue + /// + /// 与 RuntimeAspectContext 的区别仅在实现机制: + /// - 方法调度:IAspectInvokeDelegate(编译时委托)替代 MethodReflector(DynamicMethod) + /// - 异步等待:NativeAOT 安全实现替代 Expression.Compile() 路径 + /// + public override async Task Complete() + { + if (_implementation == null || _implementationMethod == null) + { + await Break(); + return; + } + var returnValue = _invokeDelegate.Invoke(_implementation, Parameters); + await AwaitIfAsyncNativeAotSafe(returnValue); + ReturnValue = returnValue; + } + + /// + /// NativeAOT 安全的异步等待实现。 + /// 不使用 Expression.Compile(),对 ValueTask 使用 MethodInfo.Invoke + /// 调用 .AsTask()(标准反射,非 Emit,NativeAOT 兼容)。 + /// + private static async Task AwaitIfAsyncNativeAotSafe(object returnValue) + { + switch (returnValue) + { + case null: + break; + case Task task: + await task; + break; + case ValueTask valueTask: + await valueTask; + break; + default: + // ValueTask 不匹配 ValueTask(不同类型),走此分支 + var type = returnValue.GetType(); + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ValueTask<>)) + { + // 使用 MethodInfo.Invoke 调用 .AsTask() + // MethodInfo.Invoke 是 NativeAOT 安全的(标准反射,非 Emit) + var asTaskMethod = type.GetMethod(nameof(ValueTask.AsTask))!; + var task = (Task)asTaskMethod.Invoke(returnValue, null)!; + await task; + } + break; + } + } + + // Break()、Invoke()、Dispose() 与 RuntimeAspectContext 完全一致(略) + // 注:Break() 中调用的 returnType.GetDefaultValue() 来自 AspectCore.Extensions.Reflection, + // 但该方法内部仅使用 Type.GetTypeCode + Activator.CreateInstance(标准反射),不依赖 Emit, + // NativeAOT 安全。Phase 2 标注 [RequiresDynamicCode] 时需排除此方法。 + } +} +``` + +### 3.5 SG 生成的代理中直接调用 DIM 重载 + +SG 生成的代理类只需持有 `IAspectContextFactory`,直接调用新重载——DIM 保证即使第三方实现未 override 也能编译运行(默认回退到无 delegate 路径): + +```csharp +// SG 生成的代理类字段——与之前完全相同 +private readonly IAspectContextFactory _aspectContextFactory; + +// 代理方法体中——直接调用两参数重载,无需 cast 或 null 检查 +var __context = _aspectContextFactory.CreateContext(__ctx, __InvokeDelegates.DoWork); +``` + +**回退行为**:当第三方 `IAspectContextFactory` 实现未 override DIM 方法时,默认实现调用 `CreateContext(ctx)`,走 `RuntimeAspectContext` + MethodReflector 路径。在非 NativeAOT 环境下完全可用;在 NativeAOT + Strict 模式下会在 `Complete()` 阶段抛出明确异常。 + +### 3.7 Source Generator 生成调度委托 + +对每个被拦截方法,Source Generator 生成一个强类型的 `IAspectInvokeDelegate` 实现: + +```csharp +// === 普通同步方法 === +[EditorBrowsable(EditorBrowsableState.Never)] +internal sealed class __InvokeDelegate_IService_DoWork : IAspectInvokeDelegate +{ + public object Invoke(object instance, object[] parameters) + { + var typed = (ServiceImpl)instance; + var p0 = (string)parameters[0]; + var p1 = (int)parameters[1]; + var result = typed.DoWork(p0, p1); + return result; // boxing if value type + } +} + +// === void 方法 === +internal sealed class __InvokeDelegate_IService_Process : IAspectInvokeDelegate +{ + public object Invoke(object instance, object[] parameters) + { + ((ServiceImpl)instance).Process(); + return null; + } +} + +// === ref/out 参数 === +internal sealed class __InvokeDelegate_IService_TryParse : IAspectInvokeDelegate +{ + public object Invoke(object instance, object[] parameters) + { + var typed = (ServiceImpl)instance; + var p0 = (string)parameters[0]; + var p1_ref = (int)(parameters[1] ?? default(int)); + var result = typed.TryParse(p0, out p1_ref); + parameters[1] = p1_ref; // 写回 ref/out 到管道共享的 parameters 数组 + return result; + } +} + +// === async Task / ValueTask 方法 === +internal sealed class __InvokeDelegate_IService_GetDataAsync : IAspectInvokeDelegate +{ + public object Invoke(object instance, object[] parameters) + { + var typed = (ServiceImpl)instance; + var p0 = (int)parameters[0]; + // 返回 Task/ValueTask 对象本身 + // Complete() → AwaitIfAsyncNativeAotSafe 等待完成 + // ReturnValue 保持原对象,上层 inline activation 做 switch 提取 + return typed.GetDataAsync(p0); + } +} + +// === ref return 方法 === +internal sealed class __InvokeDelegate_IService_GetRef : IAspectInvokeDelegate +{ + public object Invoke(object instance, object[] parameters) + { + // 返回 unwrapped T(boxed),代理侧用 StrongBox 承接 + return ((ServiceImpl)instance).GetValue(); + } +} +``` + +**委托实例作为静态单例**: + +```csharp +private static class __InvokeDelegates +{ + public static readonly IAspectInvokeDelegate DoWork = new __InvokeDelegate_IService_DoWork(); + public static readonly IAspectInvokeDelegate GetDataAsync = new __InvokeDelegate_IService_GetDataAsync(); + // ... +} +``` + +### 3.8 SG 生成的代理方法体——统一 inline activation + +**设计决策**:将 sync 和 async 方法统一为 inline activation,直接调用 `IAspectContextFactory.CreateContext(ctx, delegate)` DIM 重载。不再经过 `AspectActivator`,避免修改 DynamicProxy 路径代码。 + +**sync 方法**: + +```csharp +public string DoWork(string name, int count) +{ + if (!ShouldIntercept(__Meta.Service_DoWork, __Meta.Impl_DoWork)) + return base.DoWork(name, count); + + var __args = new object[] { name, count }; + var __ctx = new AspectActivatorContext( + __Meta.Service_DoWork, __Meta.Impl_DoWork, __Meta.Proxy_DoWork, + __Meta.Service_DoWork, _implementation, this, __args); + + var __context = _aspectContextFactory.CreateContext(__ctx, __InvokeDelegates.DoWork); + try + { + var __builder = _aspectBuilderFactory.GetBuilder( + __Meta.Service_DoWork, __Meta.Impl_DoWork, __Meta.Service_DoWork); + var __pipeline = __builder.Build(); + var __task = __pipeline(__context); + if (__task.IsFaulted) + System.Runtime.ExceptionServices.ExceptionDispatchInfo + .Capture(__task.Exception!.InnerException!).Throw(); + if (!__task.IsCompleted) + NoSyncContextScope.Run(__task); + return (string)__context.ReturnValue; + } + catch (AspectInvocationException) { throw; } + catch (Exception __ex) + { + if (ThrowAspectException) + throw new AspectInvocationException(__context, __ex); + throw; + } + finally { _aspectContextFactory.ReleaseContext(__context); } +} +``` + +**async Task\ 方法**: + +```csharp +public async Task GetDataAsync(int id) +{ + if (!ShouldIntercept(__Meta.Service_GetDataAsync, __Meta.Impl_GetDataAsync)) + return await base.GetDataAsync(id); + + var __args = new object[] { id }; + var __ctx = new AspectActivatorContext( + __Meta.Service_GetDataAsync, __Meta.Impl_GetDataAsync, __Meta.Proxy_GetDataAsync, + __Meta.Service_GetDataAsync, _implementation, this, __args); + + var __context = _aspectContextFactory.CreateContext(__ctx, __InvokeDelegates.GetDataAsync); + try + { + var __builder = _aspectBuilderFactory.GetBuilder( + __Meta.Service_GetDataAsync, __Meta.Impl_GetDataAsync, __Meta.Service_GetDataAsync); + var __pipeline = __builder.Build(); + await __pipeline(__context); + // ReturnValue 是 Task(由 Complete() 等待完成后保留原对象) + // 或者是拦截器直接设置的 string 值 + switch (__context.ReturnValue) + { + case Task taskResult: return await taskResult; + case string directResult: return directResult; + case null: return default; + default: + throw new AspectInvocationException(__context, + new InvalidCastException($"Cannot cast ReturnValue of type '{__context.ReturnValue.GetType()}' to 'Task' or 'string'.")); + } + } + catch (AspectInvocationException) { throw; } + catch (Exception __ex) + { + if (ThrowAspectException) + throw new AspectInvocationException(__context, __ex); + throw; + } + finally { _aspectContextFactory.ReleaseContext(__context); } +} +``` + +**非泛型 Task / ValueTask 返回方法**: + +```csharp +public async Task ProcessAsync(int id) +{ + if (!ShouldIntercept(__Meta.Service_ProcessAsync, __Meta.Impl_ProcessAsync)) + { await base.ProcessAsync(id); return; } + + var __args = new object[] { id }; + var __ctx = new AspectActivatorContext(/* ... */); + var __context = _aspectContextFactory.CreateContext(__ctx, __InvokeDelegates.ProcessAsync); + try + { + var __builder = _aspectBuilderFactory.GetBuilder(/* ... */); + await __builder.Build()(__context); + // 非泛型 Task:ReturnValue 是 Task 对象,已在 Complete() 中 await 完成 + // 不需要提取结果值 + } + catch (AspectInvocationException) { throw; } + catch (Exception __ex) + { + if (ThrowAspectException) throw new AspectInvocationException(__context, __ex); + throw; + } + finally { _aspectContextFactory.ReleaseContext(__context); } +} +``` + +**IAsyncEnumerable\ 方法**: + +C# 禁止在含 catch 子句的 try 块中 yield(CS1626)。因此 SG 将管道执行和迭代拆分为两层——与现有 `AspectActivator.InvokeAsyncEnumerableCore` 模式一致: + +```csharp +// 外层入口:iterator 方法,try/finally(无 catch)包裹 yield +public async IAsyncEnumerable StreamDataAsync(int count, + [EnumeratorCancellation] CancellationToken ct = default) +{ + if (!ShouldIntercept(__Meta.Service_StreamDataAsync, __Meta.Impl_StreamDataAsync)) + { + await foreach (var item in base.StreamDataAsync(count, ct).WithCancellation(ct)) + yield return item; + yield break; + } + + var __args = new object[] { count, ct }; + var __ctx = new AspectActivatorContext(/* ... */); + var __context = _aspectContextFactory.CreateContext(__ctx, __InvokeDelegates.StreamDataAsync); + try + { + // 管道执行(含 catch)拆到 helper 方法 + var __enumerable = await __ExecutePipelineForStreamDataAsync(__context); + if (__enumerable == null) yield break; + + // 迭代结果——yield 在 try/finally(无 catch)中,合法 + var __enumerator = __enumerable.WithCancellation(ct).GetAsyncEnumerator(); + try + { + while (true) + { + string __item; + try + { + if (!await __enumerator.MoveNextAsync()) break; + __item = __enumerator.Current; + } + catch (Exception __ex) + { + if (ThrowAspectException && __ex is not AspectInvocationException) + throw new AspectInvocationException(__context, __ex); + throw; + } + yield return __item; // yield 在 try 外层(此层无 catch) + } + } + finally + { + await __enumerator.DisposeAsync(); + } + } + finally { _aspectContextFactory.ReleaseContext(__context); } +} + +// 内层 helper:执行管道(含 catch),返回 IAsyncEnumerable +private async Task> __ExecutePipelineForStreamDataAsync( + AspectContext __context) +{ + try + { + var __builder = _aspectBuilderFactory.GetBuilder(/* ... */); + var __task = __builder.Build()(__context); + if (__task.IsFaulted) + ExceptionDispatchInfo.Capture(__task.Exception!.InnerException!).Throw(); + if (!__task.IsCompleted) + await __task; + return __context.ReturnValue as IAsyncEnumerable; + } + catch (AspectInvocationException) { throw; } + catch (Exception __ex) + { + if (ThrowAspectException) + throw new AspectInvocationException(__context, __ex); + throw; + } +} +``` + +### 3.9 开放式泛型方法处理 + +对开放式泛型方法(`void Process(T item)`),Source Generator 无法为所有可能的 `T` 预生成委托。 + +**策略**: + +1. **SG 为每个在项目中发现的具体类型实参生成委托**:扫描调用点的已知具体类型。 + +2. **用户可通过 `[AspectCoreGenericHint]` 注册额外类型**: + ```csharp + [assembly: AspectCoreGenericHint(typeof(IService), nameof(IService.Process), typeof(int))] + [assembly: AspectCoreGenericHint(typeof(IService), nameof(IService.Process), typeof(string))] + ``` + +3. **运行时 fallback**:对未预生成的类型参数组合,`IAspectInvokeDelegate` 实现内部使用 `MethodInfo.MakeGenericMethod(typeArgs).Invoke()`(标准反射,NativeAOT 兼容——前提是目标方法在 rd.xml/TrimmerRoots 中保留了元数据)。 + +4. **NativeAOT + Strict 模式**:SG 在编译时发出诊断 `ACSG0101`: + ``` + warning ACSG0101: Method 'IService.Process' is an open generic. NativeAOT-safe delegates + are only generated for known type arguments. Add [AspectCoreGenericHint] for concrete types, + or runtime fallback may throw in NativeAOT environments. + ``` + +5. **运行时行为**: + - `Strict = false`:尝试 `MethodInfo.Invoke()`(标准反射调用,NativeAOT 兼容但性能低) + - `Strict = true`:抛出 `InvalidOperationException`,提示用户添加 GenericHint + +--- + +## 四、实施分阶段 + +### Phase 1:编译时调度委托(消除 MethodReflector 和 Expression.Compile 依赖) + +| 步骤 | 改动 | 影响范围 | +|------|------|----------| +| 1.1 | 新增 `IAspectInvokeDelegate` 接口 | `AspectCore.Abstractions` | +| 1.2 | `IAspectContextFactory` 新增 DIM `CreateContext` 重载 | `AspectCore.Abstractions` | +| 1.3 | 新增 `SourceGeneratedAspectContext`(含 NativeAOT 安全 AwaitIfAsync) | `AspectCore.Core` | +| 1.4 | `AspectContextFactory` override DIM 重载 | `AspectCore.Core` | +| 1.5 | `ScopeAspectContextFactory` override DIM 重载 | `AspectCore.Extensions.AspectScope` | +| 1.6 | SG 为每个被拦截方法生成 `IAspectInvokeDelegate` 实现 | `AspectCore.SourceGenerator` | +| 1.7 | SG 将 sync/async/IAsyncEnumerable 方法统一为 inline activation + factory cast | `AspectCore.SourceGenerator` | + +**验收**: +- SG 引擎路径下,`MethodReflector` 和 `Expression.Compile()` 均不在调用栈中 +- DynamicProxy 路径零变更:`IAspectContextFactory` 接口不变,`AspectActivatorContext` struct 不变,`RuntimeAspectContext` 不变 +- 现有第三方 `IAspectContextFactory` 实现无需改动即可编译运行(DIM 默认实现兜底,自动回退到原路径) + +### Phase 2:注解 + 门禁 + 验证 + +| 步骤 | 改动 | 影响范围 | +|------|------|----------| +| 2.1 | 为 `AspectCore.Extensions.Reflection` 全库公开 API 加 `[RequiresDynamicCode]` | 标注 | +| 2.2 | 为 `RuntimeAspectContext` 和 `AspectActivator` 中依赖 Reflection 库的路径加 `[RequiresDynamicCode]` | 标注 | +| 2.3 | SG 增加 `ACSG0101` 诊断(开放式泛型方法 NativeAOT 覆盖警告) | `AspectCore.SourceGenerator` | +| 2.4 | 新增 NativeAOT 示例项目(`true`) | `samples/` | +| 2.5 | CI 增加 NativeAOT publish + 运行验证步骤 | `.github/workflows/` | + +**验收**:NativeAOT 发布的示例项目可运行,拦截器行为正确。 + +### Phase 3:性能验证 + +| 步骤 | 改动 | 影响范围 | +|------|------|----------| +| 3.1 | BenchmarkDotNet 对比:SG 新路径 vs SG 旧路径 vs DynamicProxy 路径 | `benchmarks/` | +| 3.2 | 验证 DynamicProxy 路径零性能回退 | CI 门禁 | +| 3.3 | 验证 SG 路径性能不低于旧 SG 路径 | CI 门禁 | + +**验收**:两条路径均无统计显著的性能回退。 + +--- + +## 五、兼容性设计 + +### 5.1 TFM 变更 + +本次改动将所有包的 TFM 从 `net9.0;net8.0;net7.0;net6.0;netstandard2.1;netstandard2.0` 收窄为 `net10.0;net9.0;net8.0;net6.0`。 + +**理由**: +- NativeAOT 要求 .NET 7+,DIM 要求 .NET Core 3.0+ +- netstandard2.0/2.1 的唯一场景是 .NET Framework 遗留项目——不会使用 SG 引擎和 NativeAOT +- .NET 6 虽已 EOL,但作为 AOP 框架的最低门槛仍有覆盖价值(大量存量项目) +- .NET 7 已 EOL,可省略 + +### 5.2 DynamicProxy 路径兼容性保证 + +| 维度 | 保证 | +|------|------| +| `AspectActivatorContext` struct | **不变**——不增加字段、不修改构造函数 | +| `IAspectContextFactory` 接口 | **不变**——不新增方法、不修改签名 | +| `RuntimeAspectContext` | **不变**——代码和行为均保留 | +| `AspectActivator` | **不变**——sync/async/IAsyncEnumerable 路径均保留 | +| `ILEmitVisitor` 生成的代理 | **不变**——调用原 `CreateContext` 方法 | +| `MethodReflector` | **不变**——DynamicProxy 路径继续使用 | +| DI 注册和解析 | **不变**——不影响服务注册 | + +### 5.3 第三方兼容性 + +| 场景 | 行为 | +|------|------| +| 第三方 `IAspectContextFactory` 未 override DIM 方法 | DIM 默认实现调用 `CreateContext(ctx)` → `RuntimeAspectContext`。非 NativeAOT 环境正常工作。 | +| 第三方 `IAspectContextFactory` override 了 DIM 方法 | SG 路径完全走 NativeAOT 安全路径 | +| Windsor / Autofac / LightInject 扩展 | 纯 DynamicProxy 使用不受影响。如需 SG NativeAOT 支持,需 override DIM 方法(一次性适配) | + +### 5.4 二进制兼容分析 + +| 包 | 影响 | +|----|------| +| `AspectCore.Abstractions` | TFM 收窄(breaking for ns2.0 consumers);新增接口 `IAspectInvokeDelegate`;`IAspectContextFactory` 新增 DIM 方法——**对 net6.0+ 消费者非 breaking** | +| `AspectCore.Core` | 新增类 `SourceGeneratedAspectContext`;`AspectContextFactory` 新增方法重载——**非 breaking** | +| `AspectCore.SourceGenerator` | 生成代码变更——用户重新编译后生效 | +| `AspectCore.Extensions.AspectScope` | `ScopeAspectContextFactory` 新增方法重载——**非 breaking** | + +### 5.4 引擎选择矩阵 + +| 引擎 | NativeAOT 兼容 | 默认 | 备注 | +|------|----------------|------|------| +| DynamicProxy | 否 | 是 | 运行时织入,功能最完整 | +| SourceGenerator | 是(Phase 1-2 完成后) | 否 | 编译时生成,需用户 opt-in | +| Auto | 条件兼容 | 否 | SG 优先回退 DP;NativeAOT 下 DP 回退不可用会抛异常 | + +### 5.5 NativeAOT 下的用户使用方式 + +```csharp +var services = new ServiceCollection(); +services.AddDynamicProxy(); +services.ConfigureDynamicProxyEngine(options => +{ + options.Engine = ProxyEngine.SourceGenerator; + options.Strict = true; +}); +``` + +--- + +## 六、关键设计决策 + +### D1:为何引入 `IAspectInvokeDelegate` 而非直接内联调用? + +拦截器管道的架构决定了 `Complete()` 是在管道最内层被调用的——此时控制权已经离开了 SG 生成的代理方法体。管道是通用的 `AspectDelegate` 链,无法持有强类型的直接调用。因此需要一个通过 Factory → Context 传递的间接调度机制。 + +### D2:为何用 DIM 而非独立接口? + +TFM 收窄到 net6.0+ 后,所有目标 runtime 支持 Default Interface Methods。DIM 相比独立接口的优势: +- SG 代理直接调用 `_aspectContextFactory.CreateContext(ctx, delegate)`,无需 cast +- 第三方 `IAspectContextFactory` 实现无需改动——DIM 默认实现兜底 +- 减少 1 个接口定义,方案更简洁 + +### D3:为何 `SourceGeneratedAspectContext` 不复用 `AspectContextRuntimeExtensions.AwaitIfAsync`? + +`AwaitIfAsync` 扩展方法对 `ValueTask` 使用 `Expression.Compile()` 缓存委托做类型转换。这在 NativeAOT 下不可用。`SourceGeneratedAspectContext` 提供自己的 `AwaitIfAsyncNativeAotSafe` 实现,对 `ValueTask` 使用 `MethodInfo.Invoke` 调用 `.AsTask()`——这是标准反射(非 Emit),NativeAOT 兼容。 + +性能影响:`MethodInfo.Invoke` 比 `Expression.Compile()` 缓存的委托慢,但这只影响 `ValueTask` 返回方法的首次等待路径,且发生在 I/O 等待之后。实际 benchmark 中可忽略。 + +### D4:为何不修改管道为泛型? + +泛型管道(`AspectDelegate`)可避免 boxing,但会引入 breaking change(重写所有拦截器接口)。超出 P0-3 范围。Boxing 与 DynamicProxy 路径持平,不是回退。 + +### D5:为何将 async 方法统一为 inline activation? + +当前 SG 的 async 方法走 `AspectActivator.InvokeTask(ctx)`,内部使用 `_aspectContextFactory.CreateContext(ctx)` → `RuntimeAspectContext`。这条路径不知道 `IAspectInvokeDelegate`。 + +方案对比: +- 改造 `AspectActivator` 增加 delegate 参数 → 侵入 DynamicProxy 路径代码 +- 扩展 `AspectActivatorContext` struct → 已否决(二进制不兼容 + 性能) +- async 改为 inline activation → 与 sync 一致,factory cast 传入 delegate,零侵入 + +选择 inline activation:DynamicProxy 路径 `AspectActivator` 完全不变,SG sync/async 生成模式统一。 + +### D6:ref return 方法处理 + +生成的委托返回 unwrapped value(boxed T),代理侧使用 `StrongBox` 承接 `context.ReturnValue` 并通过 `ref __refBox.Value` 返回。与现有 SG 实现一致。 + +### D7:`IAsyncEnumerable` 处理策略 + +也走 inline activation(§3.8)。delegate 的 `Invoke()` 返回 `IAsyncEnumerable` 对象,`Complete()` 中 `AwaitIfAsyncNativeAotSafe` 对其不做任何操作(switch 不匹配任何 case),`ReturnValue` 保持原对象。代理方法体通过 `await foreach` 迭代结果。 + +--- + +## 七、风险评估 + +| 风险 | 等级 | 缓解 | +|------|------|------| +| SG 生成代码膨胀 | 中 | 每个方法:委托类 ~10 行 + inline activation ~25-30 行。50 个被拦截方法约 2000 行生成代码——可接受 | +| 开放式泛型覆盖不全 | 中 | Strict 模式 + `ACSG0101` 编译时诊断 + `[AspectCoreGenericHint]` | +| `MethodInfo.Invoke` 在 `ValueTask` await 路径的性能 | 低 | 仅 `ValueTask` 返回方法首次等待涉及,且在 I/O 等待之后,实际可忽略 | +| `IAsyncEnumerable` inline activation 的复杂度 | 低 | 已采用两层拆分模式(helper 方法执行管道 + iterator 方法做 yield),与 AspectActivator 现有实现一致 | +| 拦截器修改 Parameters 后 delegate 行为 | 低 | delegate 接收的是同一个 `parameters` 数组引用(管道共享),修改可见 | +| Boxing 开销 | 低 | 与 DynamicProxy 路径完全一致,非回退 | +| 第三方 factory 在 NativeAOT 下回退到 RuntimeAspectContext | 低 | DIM 默认回退到无 delegate 路径;文档明确要求 NativeAOT 场景 override DIM 方法;非 NativeAOT 环境回退正常 | + +--- + +## 八、替代方案对比 + +| 方案 | 描述 | 优势 | 劣势 | 结论 | +|------|------|------|------|------| +| **A: 本方案** | 独立接口 + 编译时委托 + inline activation | 零 breaking change、DP 零影响、全 TFM 兼容 | 仍有 boxing;IAsyncEnumerable yield 限制需处理 | 采用 | +| B: 独立接口 | 引入 `ISourceGeneratedContextFactory` + `as` cast | 兼容 netstandard2.0 | 多一个接口、需要 cast、代码更复杂 | 不再需要(TFM 已收窄) | +| C: 扩展 struct | `AspectActivatorContext` 加字段 | 不需改 activation 模式 | 二进制不兼容、性能回退 | 否决 | +| D: 重写管道为泛型 | 全泛型 `AspectContext` | 消除 boxing | Breaking change | 否决 | +| E: 仅标注 | 加 `[RequiresDynamicCode]` | 零改动 | NativeAOT 下 AOP 不可用 | 否决 | + +--- + +## 九、测试策略 + +### 9.1 单元测试 + +- `IAspectInvokeDelegate` 各类型方法的委托实现正确性 +- `SourceGeneratedAspectContext.Complete()` 与 `RuntimeAspectContext.Complete()` 行为一致性 +- `AwaitIfAsyncNativeAotSafe` 对 Task / ValueTask / ValueTask\ 的正确性 +- DIM 默认实现回退验证(第三方 factory 未 override 时走 RuntimeAspectContext) +- 开放式泛型方法的 Strict / non-Strict 行为 + +### 9.2 E2E 测试 + +- SG + sync 方法 + 拦截器修改 Parameters → delegate 使用修改后的数组 +- SG + async Task\ + 拦截器链 → 返回值正确 +- SG + async Task(非泛型)→ 正确完成 +- SG + ValueTask\ + 拦截器链 → 返回值正确 +- SG + ValueTask(非泛型)→ 正确完成 +- SG + IAsyncEnumerable\ → 迭代正确 +- SG + ref return → StrongBox 传递正确 +- SG + ref/out 参数 → 写回正确 +- SG + 多拦截器堆叠 → 管道执行顺序正确 +- SG + 自定义 factory(未 override DIM)→ 优雅回退到 RuntimeAspectContext +- DynamicProxy 引擎全回归 → 确认零影响 +- `AspectInvocationException` 包装行为一致性 + +### 9.3 性能 Benchmark + +- `Invoke` hot path:SG 新路径 vs SG 旧路径 vs DynamicProxy +- `InvokeTask` hot path:同上 +- `AwaitIfAsyncNativeAotSafe` vs `AwaitIfAsync`:ValueTask\ 路径对比 + +--- + +## 十、交付物清单 + +- [ ] `IAspectInvokeDelegate` 接口定义 +- [ ] `IAspectContextFactory` DIM 重载(`AspectCore.Abstractions`) +- [ ] `SourceGeneratedAspectContext` 实现(`AspectCore.Core`) +- [ ] `AspectContextFactory` override DIM 重载(`AspectCore.Core`) +- [ ] `ScopeAspectContextFactory` override DIM 重载(`AspectCore.Extensions.AspectScope`) +- [ ] Source Generator: 委托类生成器 +- [ ] Source Generator: sync/async/IAsyncEnumerable 统一 inline activation +- [ ] Source Generator: `ACSG0101` 开放式泛型诊断 +- [ ] `[RequiresDynamicCode]` 全量标注 +- [ ] NativeAOT 示例项目 +- [ ] CI NativeAOT 验证流水线 +- [ ] BenchmarkDotNet 性能对比 +- [ ] 单元测试 + E2E 测试 +- [ ] 文档更新 + +--- + +*设计方案 v4 (final) — TFM 收窄到 net6.0+,采用 DIM 简化接口设计,去掉独立 ISourceGeneratedContextFactory。已批准,可进入实施。* diff --git a/docs/development/building.md b/docs/development/building.md index 93d12a87..ecaf2268 100644 --- a/docs/development/building.md +++ b/docs/development/building.md @@ -7,7 +7,7 @@ 仓库根目录没有 `global.json`,因此不锁定具体 SDK 版本,但目标框架决定了你需要安装的 SDK: - 测试项目 `AspectCore.Core.Tests` 与 `AspectCore.E2E.Tests` 目标框架为 `net10.0;net9.0;net8.0;net6.0`。要编译并运行完整测试,需要安装 **.NET 10 SDK**。 -- 源码包最低目标框架为 `net6.0`(部分包还包含 `netstandard2.0`/`netstandard2.1`),运行多目标构建时需要相应的 .NET 6/7/8/9 运行时。 +- 源码包最低目标框架为 `net6.0`,其余目标为 `net8.0`/`net9.0`/`net10.0`(`AspectCore.SourceGenerator` 例外,为 `netstandard2.0` 的 Roslyn 分析器),运行多目标构建时需要相应的 .NET 6/8/9/10 运行时。 - CI 通过 `actions/setup-dotnet` 显式安装以下 SDK:`6.0.x`、`8.0.x`、`9.0.x`、`10.0.x`(见 `.github/workflows/build-ci.yml` 与 `.github/workflows/build-pr-ci.yml`)。本地对齐这几个版本即可覆盖所有目标框架。 验证本地 SDK: @@ -61,10 +61,10 @@ dotnet test tests/AspectCore.Core.Tests/AspectCore.Core.Tests.csproj -f net8.0 | 项目 | 目标框架 | 说明 | |------|----------|------| -| `AspectCore.Abstractions`、`AspectCore.Core`、`AspectCore.Extensions.Reflection` | `net9.0;net8.0;net7.0;net6.0;netstandard2.1;netstandard2.0` | 核心包多目标,兼容 .NET Framework(经 netstandard2.0) | +| `AspectCore.Abstractions`、`AspectCore.Core`、`AspectCore.Extensions.Reflection` | `net10.0;net9.0;net8.0;net6.0` | 核心包多目标,最低支持 `net6.0` | | `AspectCore.SourceGenerator` | `netstandard2.0` | 编译时引擎需以 `netstandard2.0` 供 Roslyn 加载;`LangVersion=latest` | -| `AspectCore.Extensions.AspNetCore` | `net9.0;net8.0;net7.0;net6.0` | 仅 `net6.0` 及以上 | -| 容器/宿主等扩展包 | 以各 `*.csproj` 为准(多为 `net6.0` 及以上,含 netstandard 目标) | 详见 [项目结构](./project-structure.md) 与 [模块与包结构设计](../architecture/module-design.md) | +| `AspectCore.Extensions.AspNetCore` | `net10.0;net9.0;net8.0;net6.0` | 仅 `net6.0` 及以上 | +| 容器/宿主等扩展包 | 以各 `*.csproj` 为准(多为 `net10.0;net9.0;net8.0;net6.0`) | 详见 [项目结构](./project-structure.md) 与 [模块与包结构设计](../architecture/module-design.md) | | 测试项目 | 多为 `net10.0;net9.0;net8.0;net6.0` 或 `net9.0;net8.0;net6.0` | 具体差异见 [测试策略](../testing/testing-strategy.md) | 多目标构建会为每个目标框架各产出一份程序集;因此本地缺少某个运行时会导致该目标框架的编译或测试步骤失败。 @@ -73,8 +73,8 @@ dotnet test tests/AspectCore.Core.Tests/AspectCore.Core.Tests.csproj -f net8.0 构建配置集中在 `build/` 目录与两个 `Directory.Build.props`,各 `*.csproj` 通过 `Import` 引入: -- `build/version.props` — 产品版本。当前 `VersionMajor=2`、`VersionMinor=7`、`VersionPatch=0`,`VersionQuality` 为空,因此 `VersionPrefix=2.7.0`。CI 在无 Git tag 时追加 `-preview-<时间戳>`。 -- `build/common.props` — 公共包元数据(`Authors=Lemon`、`Product=AspectCore Framework`、仓库地址等),并 `Import` 了 `sign.props` 与 `version.props`。其中设置 `LangVersion=10.0`;注释说明:10.0 是最低目标框架 `net6.0` 支持的最新稳定 C# 版本。核心包源码即在此约束下编写。 +- `build/version.props` — 产品版本。当前 `VersionMajor=3`、`VersionMinor=0`、`VersionPatch=0`、`VersionQuality=rc.1`,因此 `VersionPrefix=3.0.0`、`VersionSuffix=rc.1`。CI 在无 Git tag 时追加 `-preview-<时间戳>`。 +- `build/common.props` — 公共包元数据(`Authors=Lemon`、`Product=AspectCore Framework`、仓库地址等),并 `Import` 了 `sign.props` 与 `version.props`。其中设置 `LangVersion=13.0`;注释说明:13.0 启用了 NativeAOT AOP 支持所需的默认接口方法、静态抽象成员等现代特性,同时保持与最低目标框架 `net6.0` 的兼容。核心包源码即在此约束下编写。 - `build/sign.props` 与 `build/aspectcore.snk` — 强名称签名配置与密钥。 - `src/Directory.Build.props` — 对 `src/` 下所有项目启用 .NET 分析器(`EnableNETAnalyzers=true`、`AnalysisLevel=latest`、`AnalysisMode=Default`、`EnforceExtendedAnalyzerRules=true`)。这些是提示性诊断,不作为硬性失败门槛。 - `tests/Directory.Build.props` — 为所有测试项目统一引入 `coverlet.msbuild`(版本 `6.0.2`,`PrivateAssets=all`),用于覆盖率采集。 diff --git a/docs/development/code-review-guidelines.md b/docs/development/code-review-guidelines.md index 37c8f05d..00884c7a 100644 --- a/docs/development/code-review-guidelines.md +++ b/docs/development/code-review-guidelines.md @@ -21,7 +21,7 @@ | 检查项 | 说明 | |--------|------| | 改动范围是否合理 | 改公共接口 → solution 级验证;改单个文件 → project 级验证 | -| 是否影响多目标框架 | 代码是否在所有 TFM(net9.0/8.0/7.0/6.0/netstandard2.1/2.0)下都能编译 | +| 是否影响多目标框架 | 代码是否在所有 TFM(net10.0/9.0/8.0/6.0)下都能编译 | | 是否修改了 `build/common.props` | 如有,是否所有项目都受影响并验证 | | 命令是否与 CI 对齐 | 本地 build/test 命令是否匹配 `.github/workflows/` 中的 CI 行为 | diff --git a/docs/development/development-guidelines.md b/docs/development/development-guidelines.md index 5e37ae7f..e7de5df6 100644 --- a/docs/development/development-guidelines.md +++ b/docs/development/development-guidelines.md @@ -28,7 +28,7 @@ AspectCore 是一个由 MSBuild、NuGet、TargetFramework 和宿主框架共同 - 只补一条测试 → `--filter` 精确到那个测试类 **常见错误:** -- 仓库多目标框架(`net9.0;net8.0;net7.0;net6.0;netstandard2.1;netstandard2.0`),只按单一 TFM 写 API → 错误 +- 仓库多目标框架(`net10.0;net9.0;net8.0;net6.0`),只按单一 TFM 写 API → 错误 - 改了 `build/common.props` 却只编一个项目 → 错误 --- @@ -53,9 +53,9 @@ AspectCore 是一个由 MSBuild、NuGet、TargetFramework 和宿主框架共同 ### 4.2 再看 SDK 与目标框架约定 - 无 `global.json`,SDK 未钉死 -- `src/` 的 `LangVersion=10.0`(`build/common.props`) +- `src/` 的 `LangVersion=13.0`(`build/common.props`) - 测试项目 `LangVersion=13.0` -- 库目标框架:`net9.0;net8.0;net7.0;net6.0;netstandard2.1;netstandard2.0`(详见 AGENTS.md) +- 库目标框架:`net10.0;net9.0;net8.0;net6.0`(详见 AGENTS.md) ### 4.3 识别测试框架与质量工具链 - xUnit `2.9.2` + `Microsoft.NET.Test.Sdk 17.12.0` @@ -181,8 +181,8 @@ dotnet test tests/AspectCore.Core.Tests/AspectCore.Core.Tests.csproj \ - 改了公共接口,却只跑单个测试项目 → 漏回归 ### 9.3 忽略仓库的目标框架约定 -- 项目同时面向 `net9.0` 和 `netstandard2.0`,却直接使用只存在于新 TFM 的 API -- 应使用 `#if NET9_0_OR_GREATER` 条件编译,或抽象接口做适配 +- 项目同时面向 `net8.0` 和 `net6.0`,却直接使用只存在于新 TFM 的 API +- 应使用 `#if NET8_0_OR_GREATER` 条件编译,或抽象接口做适配 ### 9.4 在热路径里机械使用 LINQ、异常或字符串操作 - 热路径里的 LINQ 链式枚举 → 每次调用分配枚举器 diff --git a/docs/development/project-structure.md b/docs/development/project-structure.md index d5c8c7a1..66e2fc3d 100644 --- a/docs/development/project-structure.md +++ b/docs/development/project-structure.md @@ -58,7 +58,7 @@ ## 6. `build/` — 构建配置 -集中管理版本、签名与公共包属性:`version.props`(产品版本 2.7.0)、`common.props`(包元数据 + `LangVersion=10.0`)、`sign.props` + `aspectcore.snk`(强名称签名),以及 Cake 脚本(`index.cake`、`util.cake`、`version.cake`)。详见 [本地构建](./building.md)。 +集中管理版本、签名与公共包属性:`version.props`(产品版本 3.0.0-rc.1)、`common.props`(包元数据 + `LangVersion=13.0`)、`sign.props` + `aspectcore.snk`(强名称签名),以及 Cake 脚本(`index.cake`、`util.cake`、`version.cake`)。详见 [本地构建](./building.md)。 ## 7. `.github/` — CI diff --git a/docs/en/architecture/language-features.md b/docs/en/architecture/language-features.md index 4d615f01..a5a7a26f 100644 --- a/docs/en/architecture/language-features.md +++ b/docs/en/architecture/language-features.md @@ -13,8 +13,8 @@ AspectCore has two AOP engines: Current status: -- `LangVersion` is set to `10.0` (core library); the Source Generator uses `latest` -- Supports `net6.0` ~ `net9.0` + `netstandard2.0/2.1` +- `LangVersion` is set to `13.0` (core library); the Source Generator uses `latest` +- Supports `net6.0` ~ `net10.0` (`AspectCore.SourceGenerator` targets `netstandard2.0`) - The two engines follow different paths in method body generation: DynamicProxy dispatches to the `AspectActivator.Invoke*` family of methods via the `ReturnKind` enum; the Source Generator generates different inline code depending on sync/async ### 1.1 The Core AOP Emit Flow @@ -70,7 +70,6 @@ The Emit process of the compile-time Source Generator: - `ReturnKind` adds `AsyncEnumerable`; DynamicProxy and Source Generator call `IAspectActivator.InvokeAsyncEnumerable` respectively. - The async stream stays lazy: the interceptor chain and the target method execute on first enumeration; when the stream completes, is canceled, or an exception occurs during enumeration, the `AspectContext` is released. - `IAsyncDisposable.DisposeAsync()` returns a `ValueTask`, and interception is already completed via the existing `ValueTask` proxy path. -- The `netstandard2.0` target introduces `Microsoft.Bcl.AsyncInterfaces` to expose the C# 8 async interfaces. **Verification coverage** diff --git a/docs/en/architecture/module-design.md b/docs/en/architecture/module-design.md index a130a3e0..9001c589 100644 --- a/docs/en/architecture/module-design.md +++ b/docs/en/architecture/module-design.md @@ -12,7 +12,7 @@ AspectCore follows unidirectional dependencies: **contracts at the bottom, imple - Integration/feature packages depend only on `Core` or `Abstractions`, without horizontal coupling among themselves (`AspNetCore` and `DataAnnotations` are deliberate composition exceptions). - The compile-time engine (`SourceGenerator`) is independent of the runtime, and only references fully qualified names of runtime types within its generated code. -Versioning and language level are managed uniformly by `build/common.props` (`LangVersion=10.0`, product version in `build/version.props`); `SourceGenerator` itself overrides this to `LangVersion=latest`. +Versioning and language level are managed uniformly by `build/common.props` (`LangVersion=13.0`, product version in `build/version.props`); `SourceGenerator` itself overrides this to `LangVersion=latest`. ## 2. Dependency Graph @@ -68,7 +68,7 @@ A standalone library that uses `DynamicMethod` + IL emit to compile cached deleg - Reflector classes: `MethodReflector` (with `Static`/`Call`/`OpenGeneric` variants), `ConstructorReflector`, `FieldReflector`, `PropertyReflector`, `TypeReflector`, `ParameterReflector`, `CustomAttributeReflector`, all derived from `MemberReflector` — `MethodReflector.cs:10`, `MemberReflector.cs:7` - Caching: `ReflectorCacheUtils` uses `ConcurrentDictionary.GetOrAdd` to ensure each reflector is compiled only once — `Internals/ReflectorUtils.cs:8` - IL helpers: `Emit/ILGeneratorExtensions.cs` (`EmitLoadArg`/`EmitLdRef`/`EmitStRef`/type conversions, etc.) -- Dependencies: modern TFMs have no external dependencies; only `netstandard2.0` needs `System.Threading.Tasks.Extensions`, `System.Reflection.Emit.Lightweight`, and `System.Runtime.CompilerServices.Unsafe` +- Dependencies: all target frameworks (net6.0+) have no external dependencies ## 4. Runtime Core Layer diff --git a/docs/en/development/building.md b/docs/en/development/building.md index fecc37d7..2eb8ed8e 100644 --- a/docs/en/development/building.md +++ b/docs/en/development/building.md @@ -7,7 +7,7 @@ This document explains how to restore, compile, and test AspectCore locally, and The repository root has no `global.json`, so it does not pin a specific SDK version, but the target frameworks determine which SDKs you need to install: - The test projects `AspectCore.Core.Tests` and `AspectCore.E2E.Tests` target `net10.0;net9.0;net8.0;net6.0`. To compile and run the full test suite, you need the **.NET 10 SDK** installed. -- The source packages target `net6.0` at minimum (some packages also include `netstandard2.0`/`netstandard2.1`); running a multi-target build requires the corresponding .NET 6/7/8/9 runtimes. +- The source packages target `net6.0` at minimum, with additional targets `net8.0`/`net9.0`/`net10.0` (except `AspectCore.SourceGenerator`, which is a `netstandard2.0` Roslyn analyzer); running a multi-target build requires the corresponding .NET 6/8/9/10 runtimes. - CI explicitly installs the following SDKs via `actions/setup-dotnet`: `6.0.x`, `8.0.x`, `9.0.x`, `10.0.x` (see `.github/workflows/build-ci.yml` and `.github/workflows/build-pr-ci.yml`). Aligning locally with these versions covers all target frameworks. Verify your local SDKs: @@ -61,10 +61,10 @@ Different projects choose target frameworks by purpose; the authoritative source | Project | Target frameworks | Notes | |------|----------|------| -| `AspectCore.Abstractions`, `AspectCore.Core`, `AspectCore.Extensions.Reflection` | `net9.0;net8.0;net7.0;net6.0;netstandard2.1;netstandard2.0` | The core packages multi-target, compatible with .NET Framework (via netstandard2.0) | +| `AspectCore.Abstractions`, `AspectCore.Core`, `AspectCore.Extensions.Reflection` | `net10.0;net9.0;net8.0;net6.0` | The core packages multi-target, with `net6.0` as the minimum | | `AspectCore.SourceGenerator` | `netstandard2.0` | The compile-time engine must target `netstandard2.0` for Roslyn to load; `LangVersion=latest` | -| `AspectCore.Extensions.AspNetCore` | `net9.0;net8.0;net7.0;net6.0` | `net6.0` and above only | -| Container/host and other extension packages | Per each `*.csproj` (mostly `net6.0` and above, including netstandard targets) | See [Project structure](./project-structure.md) and [Module and package structure design](../architecture/module-design.md) | +| `AspectCore.Extensions.AspNetCore` | `net10.0;net9.0;net8.0;net6.0` | `net6.0` and above only | +| Container/host and other extension packages | Per each `*.csproj` (mostly `net10.0;net9.0;net8.0;net6.0`) | See [Project structure](./project-structure.md) and [Module and package structure design](../architecture/module-design.md) | | Test projects | Mostly `net10.0;net9.0;net8.0;net6.0` or `net9.0;net8.0;net6.0` | For the specific differences, see [Testing strategy](../testing/testing-strategy.md) | A multi-target build produces one assembly per target framework; therefore, missing a runtime locally will cause the compile or test step for that target framework to fail. @@ -73,8 +73,8 @@ A multi-target build produces one assembly per target framework; therefore, miss Build configuration is centralized in the `build/` directory and two `Directory.Build.props`, imported by each `*.csproj` via `Import`: -- `build/version.props` — the product version. Currently `VersionMajor=2`, `VersionMinor=7`, `VersionPatch=0`, with `VersionQuality` empty, so `VersionPrefix=2.7.0`. When there is no Git tag, CI appends `-preview-`. -- `build/common.props` — common package metadata (`Authors=Lemon`, `Product=AspectCore Framework`, repository URL, etc.), and it `Import`s `sign.props` and `version.props`. It sets `LangVersion=10.0`; the comment explains: 10.0 is the latest stable C# version supported by the lowest target framework `net6.0`. The core package source is written under this constraint. +- `build/version.props` — the product version. Currently `VersionMajor=3`, `VersionMinor=0`, `VersionPatch=0`, `VersionQuality=rc.1`, so `VersionPrefix=3.0.0` and `VersionSuffix=rc.1`. When there is no Git tag, CI appends `-preview-`. +- `build/common.props` — common package metadata (`Authors=Lemon`, `Product=AspectCore Framework`, repository URL, etc.), and it `Import`s `sign.props` and `version.props`. It sets `LangVersion=13.0`; the comment explains: 13.0 enables the Default Interface Methods, static abstract members, and other modern features needed for NativeAOT AOP support, while remaining compatible with the lowest target framework `net6.0`. The core package source is written under this constraint. - `build/sign.props` and `build/aspectcore.snk` — strong-name signing configuration and key. - `src/Directory.Build.props` — enables .NET analyzers for all projects under `src/` (`EnableNETAnalyzers=true`, `AnalysisLevel=latest`, `AnalysisMode=Default`, `EnforceExtendedAnalyzerRules=true`). These are advisory diagnostics, not a hard failure gate. - `tests/Directory.Build.props` — uniformly brings in `coverlet.msbuild` (version `6.0.2`, `PrivateAssets=all`) for all test projects, used for coverage collection. diff --git a/docs/en/development/project-structure.md b/docs/en/development/project-structure.md index cda2eb56..e844f28c 100644 --- a/docs/en/development/project-structure.md +++ b/docs/en/development/project-structure.md @@ -58,7 +58,7 @@ The repository has two benchmark directories: ## 6. `build/` — build configuration -Centralized management of version, signing, and common package properties: `version.props` (product version 2.7.0), `common.props` (package metadata + `LangVersion=10.0`), `sign.props` + `aspectcore.snk` (strong-name signing), and the Cake scripts (`index.cake`, `util.cake`, `version.cake`). For details, see [Local build](./building.md). +Centralized management of version, signing, and common package properties: `version.props` (product version 3.0.0-rc.1), `common.props` (package metadata + `LangVersion=13.0`), `sign.props` + `aspectcore.snk` (strong-name signing), and the Cake scripts (`index.cake`, `util.cake`, `version.cake`). For details, see [Local build](./building.md). ## 7. `.github/` — CI diff --git a/docs/en/getting-started/installation.md b/docs/en/getting-started/installation.md index df533014..a69fae43 100644 --- a/docs/en/getting-started/installation.md +++ b/docs/en/getting-started/installation.md @@ -36,11 +36,11 @@ The Source Generator is a separate engine from the runtime DynamicProxy. For the | Package category | Target frameworks | |--------|----------| -| Core libraries and most integration packages (`AspectCore.Abstractions`, `AspectCore.Core`, `AspectCore.Extensions.Reflection`, `AspectCore.Extensions.DependencyInjection`, Autofac/Windsor/LightInject/Hosting, Configuration, DataAnnotations, etc.) | `net9.0`, `net8.0`, `net7.0`, `net6.0`, `netstandard2.1`, `netstandard2.0` | -| `AspectCore.Extensions.AspNetCore` | `net9.0`, `net8.0`, `net7.0`, `net6.0` (no netstandard) | +| Core libraries and most integration packages (`AspectCore.Abstractions`, `AspectCore.Core`, `AspectCore.Extensions.Reflection`, `AspectCore.Extensions.DependencyInjection`, Autofac/Windsor/LightInject/Hosting, Configuration, DataAnnotations, etc.) | `net10.0`, `net9.0`, `net8.0`, `net6.0` | +| `AspectCore.Extensions.AspNetCore` | `net10.0`, `net9.0`, `net8.0`, `net6.0` | | `AspectCore.SourceGenerator` | `netstandard2.0` (analyzer convention) | -> The framework lists above come from the `TargetFrameworks` in each package's `.csproj`. The core libraries cover older runtimes through `netstandard2.0`/`netstandard2.1`; `AspectCore.Extensions.AspNetCore` targets only `net6.0` and above. +> The framework lists above come from the `TargetFrameworks` in each package's `.csproj`. The core libraries and integration packages support `net6.0` at minimum; `AspectCore.SourceGenerator`, as a Roslyn analyzer, must target `netstandard2.0` so the compiler can load it. ## Install commands diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index de1c702f..c1402460 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -36,11 +36,11 @@ Source Generator 是运行时 DynamicProxy 之外的另一套引擎。两者的 | 包类别 | 目标框架 | |--------|----------| -| 核心库与大部分集成包(`AspectCore.Abstractions`、`AspectCore.Core`、`AspectCore.Extensions.Reflection`、`AspectCore.Extensions.DependencyInjection`、Autofac/Windsor/LightInject/Hosting、Configuration、DataAnnotations 等) | `net9.0`、`net8.0`、`net7.0`、`net6.0`、`netstandard2.1`、`netstandard2.0` | -| `AspectCore.Extensions.AspNetCore` | `net9.0`、`net8.0`、`net7.0`、`net6.0`(不含 netstandard) | +| 核心库与大部分集成包(`AspectCore.Abstractions`、`AspectCore.Core`、`AspectCore.Extensions.Reflection`、`AspectCore.Extensions.DependencyInjection`、Autofac/Windsor/LightInject/Hosting、Configuration、DataAnnotations 等) | `net10.0`、`net9.0`、`net8.0`、`net6.0` | +| `AspectCore.Extensions.AspNetCore` | `net10.0`、`net9.0`、`net8.0`、`net6.0` | | `AspectCore.SourceGenerator` | `netstandard2.0`(analyzer 约定) | -> 以上框架列表来自各包 `.csproj` 的 `TargetFrameworks`。核心库通过 `netstandard2.0`/`netstandard2.1` 覆盖较老的运行时;`AspectCore.Extensions.AspNetCore` 仅面向 `net6.0` 及以上。 +> 以上框架列表来自各包 `.csproj` 的 `TargetFrameworks`。核心库与集成包最低支持 `net6.0`;`AspectCore.SourceGenerator` 作为 Roslyn 分析器需以 `netstandard2.0` 供编译器加载。 ## 安装命令 diff --git a/docs/release-notes/v3.0.0.md b/docs/release-notes/v3.0.0.md new file mode 100644 index 00000000..b81a16b5 --- /dev/null +++ b/docs/release-notes/v3.0.0.md @@ -0,0 +1,99 @@ +# 停更三年后,我用两个 AI Agent 把 AspectCore 推到了 v3.0 + +AspectCore 是我 2017 年开始做的一个 .NET AOP 框架,挂在 NCC(.NET Core Community)下面,NuGet 累计下载量过百万。2023 年 v2.4.0 之后我没怎么维护了,issue 积了 19 个,最老的挂了五年。其实也不是不想维护,就是工作忙起来之后,一个人的开源项目很容易就放下了,放下越久越不想捡。 + +今年我把它捡了回来,但做法变了。我拉了两个 AI coding agent 进团队,一个写代码,一个审代码,我自己只管定方向。十天后发了 v3.0.0-beta.1。听起来像噱头,但从我的经验看,这次确实跑通了。下面把过程记下来,也包括中间踩的坑和还没解决的问题。 + +--- + +## 为什么现在重新做 + +两件事赶一块了。 + +一是 .NET 在快速淘汰运行时代码生成。NativeAOT 到 .NET 10 已经是 LTS,`System.Reflection.Emit` 在 AOT 下直接报错,而 AspectCore 整个拦截管道都建在 Emit 上。不动就等着被淘汰。Castle DynamicProxy 到今天也没解决同样的问题,这对 AspectCore 来说是个窗口,但窗口不会一直开着。 + +二是我有个私心:想看看 AI Agent 能不能真的交付一个完整版本,不是 demo 级别,是能发版的那种。之前试过让 Agent 写点零碎的功能,但从设计到实现到测试到发版的完整迭代,我也没试过。 + +--- + +## 先把 CI 搞起来 + +在让 Agent 碰代码之前,我先花了几天搞了个看起来很无聊的事:搭 CI。原来的 CI 是 AppVeyor 上一个跑不动的老 pipeline,我迁到 GitHub Actions,然后一层层加门禁:单测覆盖率 95%、E2E 覆盖率 80%、Lint、CodeQL、NativeAOT 发布验证。测试从几百个推到两千八百多个。 + +为什么非要这么做?原因很简单:Agent 的代码质量不靠我盯,靠 CI 卡。评审 Agent 的规则很死板,但死板正是我要的——CI 不绿就不合并,覆盖率差 0.13% 也打回。我们有个 PR 因为 E2E 覆盖率差 0.13%(79.87% < 80%)被打回,Agent 老老实实补了两个测试,推到 80.32% 才过。没有这层卡点,这 0.13% 的缺口就会被忽略,积少成多就是技术债。 + +同时我写了 AGENTS.md,把项目结构、构建命令、Git 规范、模块边界写清楚,相当于给 Agent 写一份新人入职手册。没有这份上下文,Agent 会猜错目录、用错命令、往错误的分支提交。花半天写清楚,后面每个任务少走一圈弯路,我觉得很值。 + +--- + +## 日常怎么跑 + +我定方向。比如"默认引擎保持 DynamicProxy 不变",这个决定基于社区迁移成本,Agent 算不出来。"TFM 最低 net6.0""先做 benchmark 再做迁移指南",这些也是我拍的。涉及用户迁移成本和社区心理的判断,目前还得人来。 + +实现 Agent 负责所有工程执行,方案它写,代码它写,测试它跑,CI 挂了它修,PR 全是它的。评审 Agent 对每个 PR 做 code review,检查 CI、审逻辑、打回说理由、通过了合并,它有独立的 GitHub 账号操作,我不在中间经手。另外还有个内部 agent 专门在设计阶段做结构化对抗,专找兼容性和平台限制的问题。 + +这套流程不是一开始就顺的。前几天 Agent 经常猜错模块边界,或者写出能跑但不符合项目约定的代码,被评审 agent 打回好几次才慢慢对齐。AGENTS.md 也是在这个过程中不断补的,不是一次写完的。 + +--- + +## 设计方案被打回四轮 + +NativeAOT 是整个 v3 最难的部分,设计方案写了四版才通过。 + +前两轮是低级错误:文档和口头结论不一致、DIM 在 netstandard2.0 下编译不过。按说实现 agent 应该在方案里就注意到这些,但它没有,倒是评审 agent 抓到了。这也说明设计阶段的对抗评审不是多余的,很多问题到实现阶段才发现的话,修的成本会高得多。 + +第三轮比较有意思。方案里 IAsyncEnumerable 的代码模板在 `try/catch` 块内放了 `yield return`,这违反 C# 语言规则 CS1626,生成出来的代码压根编译不了。这种问题不跑编译器你想不到,但评审 agent 做了语言规则层面的形式化检查,指了出来。第四轮才通过。 + +没有这几轮打回,后面实现阶段肯定要返工。Code review 顶多发现命名、边界条件这种局部问题,设计评审抓的是接口兼容性、平台限制、语言规则,后者修起来成本是前者的十倍。从我的经验看,设计评审比 code review 更该让 Agent 做对抗。 + +--- + +## 最终做了什么 + +十天,从 v2.4.0 到 v3.0.0-beta.1,ROADMAP 短中期全部清完。 + +NativeAOT 支持是主线。Source Generator 引擎在编译时为每个被拦截方法生成强类型调度委托,运行时直接调用,零 Emit、零 Expression.Compile。16 个场景在原生二进制里验证通过,冷启动 18ms。Castle DynamicProxy 不支持,Metalama 是闭源的编译时方案但不是代理模式,开源的运行时+编译时双引擎 AOP 框架里,这应该是第一个。当然这个判断基于我目前的了解,如果有遗漏欢迎指出。 + +性能这边有个剧情反转。一开始 Source Generator 引擎比 DynamicProxy 慢 38%,inline activation 每次分配 object[] 和各种 Tuple,把编译时委托的优势全吃了。三轮优化后翻过来了:SG 比 DP 快 49%,内存分配少 69%。核心手段是条件编译,net8.0+ 用 ObjectPool 和 FrozenDictionary,net6.0 走 fallback,不破坏兼容。 + +| 场景 | v2.x (DP) | v3.0 (SG) | +|------|:---:|:---:| +| 同步方法拦截 | 220 ns / 360 B | 112 ns / 112 B | +| ValueTask | 1,975 ns / 929 B | 1,514 ns / 232 B | +| NativeAOT 冷启动 | 不可用 | 18 ms | + +还做了几件事:C# 9-13 语言特性全覆盖(record、init-only、主构造函数、partial properties、ref return、async enumerable),Castle 迁移工具(双向适配器、功能对比、竞品 benchmark、迁移指南),19 个历史 issue 清理到剩 1 个。 + +有些事情还没做完。NativeAOT 目前只覆盖了 Source Generator 路径,DynamicProxy 路径在 AOT 下还是不可用;benchmark 只跑了常见场景,极端边界条件的性能数据还没系统收集;Castle 迁移工具的自动化程度还不够,很多配置需要手动改。这些留到后续版本。 + +--- + +## 跑个数 + +| | | +|---|---| +| 合并 PR | 50+ | +| 测试用例 | 2800+ | +| Benchmark 点 | 74 | +| NativeAOT 验证 | 16/16 | +| Issue 处理 | 18/19 | +| CI checks | 8 项全绿 | +| 时间 | 10 天 | + +--- + +## 一些观察 + +门禁是 Agent 协作的最低条件。没有覆盖率卡点和 CI 强制检查的项目,不适合交给 Agent 提 PR。Agent 不会自己判断"这个改动会不会搞坏别的模块",但 CI 会。 + +AGENTS.md 不是文档债务,是生产力投资。花半天把项目结构和规范写清楚,换来的是 Agent 后面每个任务少走一圈弯路。 + +当然这套方式也不是没有局限。涉及架构方向、用户迁移成本、社区接受度的判断,Agent 能给选项和 tradeoff 分析,但拍板还得是人。另外 Agent 对项目历史的理解完全依赖你提供的上下文,它不会"知道"三年前为什么做某个设计决定,除非你写进 AGENTS.md 或者在任务里说明。 + +--- + +三年前我以为这个项目到头了。现在它跑在 NativeAOT 上,冷启动 18 毫秒,同步拦截 112 纳秒。但还有不少坑要填,NativeAOT 的 DynamicProxy 路径、更全面的 benchmark、Castle 迁移的自动化,这些都是后面要做的事。 + +下次你看到一个停更的开源项目——试试拉两个 Agent。至少对我来说,这次改变了我对"一个人能维护多少项目"的预期。 + +https://github.com/dotnetcore/AspectCore-Framework diff --git a/src/AspectCore.Abstractions/AspectCore.Abstractions.csproj b/src/AspectCore.Abstractions/AspectCore.Abstractions.csproj index f9b535b1..d079ee53 100644 --- a/src/AspectCore.Abstractions/AspectCore.Abstractions.csproj +++ b/src/AspectCore.Abstractions/AspectCore.Abstractions.csproj @@ -29,7 +29,7 @@ - + diff --git a/src/AspectCore.Extensions.CastleCompat/AspectCore.Extensions.CastleCompat.csproj b/src/AspectCore.Extensions.CastleCompat/AspectCore.Extensions.CastleCompat.csproj index a13c3d71..93e7cacf 100644 --- a/src/AspectCore.Extensions.CastleCompat/AspectCore.Extensions.CastleCompat.csproj +++ b/src/AspectCore.Extensions.CastleCompat/AspectCore.Extensions.CastleCompat.csproj @@ -30,7 +30,7 @@ - + diff --git a/src/AspectCore.Extensions.Configuration/AspectCore.Extensions.Configuration.csproj b/src/AspectCore.Extensions.Configuration/AspectCore.Extensions.Configuration.csproj index eaef84e4..b9738f91 100644 --- a/src/AspectCore.Extensions.Configuration/AspectCore.Extensions.Configuration.csproj +++ b/src/AspectCore.Extensions.Configuration/AspectCore.Extensions.Configuration.csproj @@ -31,8 +31,8 @@ - - + + diff --git a/src/AspectCore.Extensions.DependencyInjection/AspectCore.Extensions.DependencyInjection.csproj b/src/AspectCore.Extensions.DependencyInjection/AspectCore.Extensions.DependencyInjection.csproj index d6b1e22d..67ffbac0 100644 --- a/src/AspectCore.Extensions.DependencyInjection/AspectCore.Extensions.DependencyInjection.csproj +++ b/src/AspectCore.Extensions.DependencyInjection/AspectCore.Extensions.DependencyInjection.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/AspectCore.Extensions.Hosting/AspectCore.Extensions.Hosting.csproj b/src/AspectCore.Extensions.Hosting/AspectCore.Extensions.Hosting.csproj index 6c690510..e72c109a 100644 --- a/src/AspectCore.Extensions.Hosting/AspectCore.Extensions.Hosting.csproj +++ b/src/AspectCore.Extensions.Hosting/AspectCore.Extensions.Hosting.csproj @@ -24,7 +24,7 @@ - + diff --git a/tests/AspectCore.Extensions.CastleCompat.Tests/AspectCore.Extensions.CastleCompat.Tests.csproj b/tests/AspectCore.Extensions.CastleCompat.Tests/AspectCore.Extensions.CastleCompat.Tests.csproj index 58e1e6f9..69aab6f9 100644 --- a/tests/AspectCore.Extensions.CastleCompat.Tests/AspectCore.Extensions.CastleCompat.Tests.csproj +++ b/tests/AspectCore.Extensions.CastleCompat.Tests/AspectCore.Extensions.CastleCompat.Tests.csproj @@ -22,7 +22,7 @@ - + From 5bf5c245b69496675d04e70868d7308ffc7fb790 Mon Sep 17 00:00:00 2001 From: Haoyang Liu Date: Sat, 25 Jul 2026 11:29:47 +0800 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20sync=20docs=20with=203.0.0-rc.1=20?= =?UTF-8?q?=E2=80=94=20fix=20package/test=20counts,=20add=20CastleCompat,?= =?UTF-8?q?=20correct=20TFM=20&=20taglines?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix structural drift: package count 14 -> 15, add AspectCore.Extensions.CastleCompat to structure/module/installation listings (zh/en) - Update test-project count: 10 xUnit + AspectCore.NativeAot.E2E - Remove stale build.cake/build.ps1 references (build is dotnet CLI driven) - README taglines: "for .NET Core and .NET Framework" -> "for .NET 6+" (root + docs zh/en) - building.md: state CastleCompat TFM exception (net10/9/8, no net6.0) --- AGENTS.md | 3 ++- README.md | 2 +- docs/README.md | 4 ++-- docs/architecture/module-design.md | 4 +++- docs/architecture/overview.md | 2 +- docs/development/building.md | 4 ++-- docs/development/project-structure.md | 18 ++++++++++-------- docs/en/README.md | 4 ++-- docs/en/architecture/module-design.md | 4 +++- docs/en/architecture/overview.md | 2 +- docs/en/development/building.md | 4 ++-- docs/en/development/project-structure.md | 18 ++++++++++-------- docs/en/getting-started/installation.md | 2 ++ docs/getting-started/installation.md | 2 ++ 14 files changed, 43 insertions(+), 30 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0de7797d..dd0b81fc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,7 +41,8 @@ Project-level AI context for the AspectCore-Framework repository. Generated from | `src/AspectCore.Extensions.Configuration/` | Configuration injection via `Microsoft.Extensions.Configuration`. | – | | `src/AspectCore.Extensions.DataAnnotations/` | DataAnnotations-based validation extension. | – | | `src/AspectCore.Extensions.DataValidation/` | Data validation extension. | – | -| `tests/` | 9 xUnit test projects. `tests/Directory.Build.props` injects `coverlet.msbuild`. | – | +| `src/AspectCore.Extensions.CastleCompat/` | Castle DynamicProxy compatibility shim for gradual migration to AspectCore. Depends on `Castle.Core`. Targets `net10.0;net9.0;net8.0`. | – | +| `tests/` | 10 xUnit test projects + `AspectCore.NativeAot.E2E` (a `PublishAot` executable, not xUnit). `tests/Directory.Build.props` injects `coverlet.msbuild`. | – | | `sample/` | 4 runnable sample projects (DI console, AspectScope, Autofac, DataAnnotations). | – | | `benchmark/` `benchmarks/` | BenchmarkDotNet projects. | – | | `docs/` | Architecture, guide, getting-started, development, testing docs (bilingual; `docs/en/` for English). | `docs/README.md` | diff --git a/README.md b/README.md index de1d5eea..02906f20 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build](https://github.com/dotnetcore/AspectCore-Framework/actions/workflows/build-ci.yml/badge.svg)](https://github.com/dotnetcore/AspectCore-Framework/actions/workflows/build-ci.yml) [![Member project of .NET Core Community](https://img.shields.io/badge/member%20project%20of-NCC-9e20c9.svg)](https://github.com/dotnetcore) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/dotnetcore/AspectCore/blob/dev/LICENSE) -AspectCore is an Aspect-Oriented Programming based cross platform framework for .NET Core and .NET Framework. +AspectCore is an Aspect-Oriented Programming based cross platform framework for .NET 6+. Core support for aspect-interceptor, dependency injection integration, web applications, data validation, and more. diff --git a/docs/README.md b/docs/README.md index bf86cc76..dd34221f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # AspectCore 文档 -> AspectCore 是面向 .NET Core 与 .NET Framework 的跨平台 AOP(面向切面编程)框架,提供动态代理拦截、依赖注入集成、Web 应用支持、数据校验等能力。 +> AspectCore 是面向 .NET 6+ 的跨平台 AOP(面向切面编程)框架,提供动态代理拦截、依赖注入集成、Web 应用支持、数据校验等能力。 本目录是 AspectCore 的完整文档。中文为主,英文版本见 [`en/`](./en/README.md)(English documentation lives under [`en/`](./en/README.md))。 @@ -31,7 +31,7 @@ 面向贡献者与深度使用者的设计文档。 - [总体架构](./architecture/overview.md) — 分层与运行流程 -- [模块与包结构设计](./architecture/module-design.md) — 14 个包的职责边界与依赖方向 +- [模块与包结构设计](./architecture/module-design.md) — 15 个包的职责边界与依赖方向 - [DynamicProxy 运行时引擎](./architecture/dynamic-proxy.md) — 基于 Reflection.Emit 的运行时代理 - [Source Generator 编译时引擎](./architecture/source-generator.md) — 基于 Roslyn 的编译时代理 - [两套引擎对比与选型](./architecture/engine-comparison.md) — DynamicProxy vs SourceGenerator vs Auto diff --git a/docs/architecture/module-design.md b/docs/architecture/module-design.md index 359fe78d..f37e8480 100644 --- a/docs/architecture/module-design.md +++ b/docs/architecture/module-design.md @@ -30,6 +30,7 @@ Extensions.Reflection ◄──┐ │ ├── Extensions.Windsor ────────────── (Castle.Windsor) ├── Extensions.LightInject ────────── (LightInject) ├── Extensions.Hosting ────────────── (MS.Hosting) → 也依赖 DependencyInjection + ├── Extensions.CastleCompat ───────── (Castle.Core) → 也依赖 DependencyInjection ├── Extensions.AspectScope ├── Extensions.DataValidation ─────── (仅依赖 Abstractions + Reflection) ├── Extensions.DataAnnotations ────── → 也依赖 DataValidation @@ -113,6 +114,7 @@ SourceGenerator(独立 Roslyn 分析器,无项目引用) | `Extensions.Windsor` | Castle Windsor | `IWindsorContainer.AddAspectCoreFacility(...)`(Facility 形式) | `Castle.Windsor 6.0.0` | | `Extensions.LightInject` | LightInject | `IServiceContainer.RegisterDynamicProxy(...)`(`Decorate` 形式) | `LightInject 6.6.4` | | `Extensions.Hosting` | 泛型主机 | `IHostBuilder.UseServiceContext()` / `UseDynamicProxy()` / `ConfigureDynamicProxy()` | `Microsoft.Extensions.Hosting`(并依赖 `DependencyInjection`) | +| `Extensions.CastleCompat` | Castle DynamicProxy 迁移 | Castle DynamicProxy 兼容层(shim),为存量 Castle 代码提供渐进迁移到 AspectCore 的适配 | `Castle.Core 5.2.1`(并依赖 `Core` + `DependencyInjection`) | 入口位置:`ServiceCollectionExtensions.cs:20`、`Autofac/ContainerBuilderExtensions.cs:16`、`Windsor/FacilityExtensions.cs:11`、`LightInject/ContainerBuilderExtensions.cs:32`、`Hosting/HostBuilderExtensions.cs:12`。 @@ -130,7 +132,7 @@ SourceGenerator(独立 Roslyn 分析器,无项目引用) ## 7. 测试、示例、基准(非发行) -- `tests/`:`AspectCore.Core.Tests`(含 `EngineParity/` 双引擎一致性测试)、`AspectCore.E2E.Tests`、各容器适配测试、`AspectCore.Extensions.Reflection.Test` 等。详见 [测试策略](../testing/testing-strategy.md)。 +- `tests/`:`AspectCore.Core.Tests`(含 `EngineParity/` 双引擎一致性测试)、`AspectCore.E2E.Tests`、各容器适配测试、`AspectCore.Extensions.Reflection.Test`、`AspectCore.Extensions.CastleCompat.Tests`,以及 NativeAOT 端到端验证项目 `AspectCore.NativeAot.E2E`(可执行程序,非 xUnit)等。详见 [测试策略](../testing/testing-strategy.md)。 - `sample/`:AspectScope、Autofac、DataAnnotations、DependencyInjection 控制台示例。 - `benchmark/`、`benchmarks/`:Core 与 Reflection 的基准项目。 diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 15b89184..6ce9402e 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -105,6 +105,6 @@ Source Generator 在**编译时**扫描带 `[AspectCoreGenerateProxy]` 的类型 ## 7. 延伸阅读 -- [模块与包结构设计](./module-design.md):逐层说明 14 个包的职责与依赖。 +- [模块与包结构设计](./module-design.md):逐层说明 15 个包的职责与依赖。 - [两套引擎对比与选型](./engine-comparison.md):何时用哪套引擎,`Strict`/`AllowRuntimeFallback`/`Auto` 的行为。 - [C# 语言特性适配](./language-features.md):各 C# 版本特性在 AOP Emit 中是否需要适配。 diff --git a/docs/development/building.md b/docs/development/building.md index ecaf2268..73841af0 100644 --- a/docs/development/building.md +++ b/docs/development/building.md @@ -64,7 +64,7 @@ dotnet test tests/AspectCore.Core.Tests/AspectCore.Core.Tests.csproj -f net8.0 | `AspectCore.Abstractions`、`AspectCore.Core`、`AspectCore.Extensions.Reflection` | `net10.0;net9.0;net8.0;net6.0` | 核心包多目标,最低支持 `net6.0` | | `AspectCore.SourceGenerator` | `netstandard2.0` | 编译时引擎需以 `netstandard2.0` 供 Roslyn 加载;`LangVersion=latest` | | `AspectCore.Extensions.AspNetCore` | `net10.0;net9.0;net8.0;net6.0` | 仅 `net6.0` 及以上 | -| 容器/宿主等扩展包 | 以各 `*.csproj` 为准(多为 `net10.0;net9.0;net8.0;net6.0`) | 详见 [项目结构](./project-structure.md) 与 [模块与包结构设计](../architecture/module-design.md) | +| 容器/宿主等扩展包 | 以各 `*.csproj` 为准:大多为 `net10.0;net9.0;net8.0;net6.0`,其中 `AspectCore.Extensions.CastleCompat` 例外为 `net10.0;net9.0;net8.0`(不含 `net6.0`) | 详见 [项目结构](./project-structure.md) 与 [模块与包结构设计](../architecture/module-design.md) | | 测试项目 | 多为 `net10.0;net9.0;net8.0;net6.0` 或 `net9.0;net8.0;net6.0` | 具体差异见 [测试策略](../testing/testing-strategy.md) | 多目标构建会为每个目标框架各产出一份程序集;因此本地缺少某个运行时会导致该目标框架的编译或测试步骤失败。 @@ -86,5 +86,5 @@ dotnet test tests/AspectCore.Core.Tests/AspectCore.Core.Tests.csproj -f net8.0 - [项目结构](./project-structure.md) — 源码、测试、示例、基准目录布局 - [贡献指南](./contributing.md) — 分支、提交、PR 流程 - [运行测试](../testing/running-tests.md) — 测试筛选与覆盖率采集 -- [模块与包结构设计](../architecture/module-design.md) — 14 个包的职责与依赖方向 +- [模块与包结构设计](../architecture/module-design.md) — 15 个包的职责与依赖方向 - [文档首页](../README.md) diff --git a/docs/development/project-structure.md b/docs/development/project-structure.md index 66e2fc3d..33b699a7 100644 --- a/docs/development/project-structure.md +++ b/docs/development/project-structure.md @@ -6,24 +6,24 @@ | 目录 | 用途 | |------|------| -| `src/` | 14 个可发布的源码包(核心 + 扩展 + 编译时引擎) | -| `tests/` | xUnit 测试项目:单元、双引擎一致性、E2E、反射、各容器集成 | +| `src/` | 15 个可发布的源码包(核心 + 扩展 + 编译时引擎) | +| `tests/` | xUnit 测试项目:单元、双引擎一致性、E2E、反射、各容器集成;另含 NativeAOT 端到端验证项目 | | `sample/` | 可运行的示例项目 | | `benchmark/` | 早期的 BenchmarkDotNet 基准项目(Core、Reflection) | | `benchmarks/` | 新的统一基准项目 `AspectCore.Benchmarks` | | `docs/` | 本文档(中文为主,英文见 `docs/en/`) | -| `build/` | 版本、签名、公共包属性与 Cake 构建脚本 | +| `build/` | 版本、签名与公共包属性(props) | | `.github/` | CI 工作流与覆盖率脚本 | -根目录还包含解决方案与工作区文件:`AspectCore-Framework.sln`、`NuGet.config`、`LICENSE`、`README.md`、`build.cake`/`build.ps1`。 +根目录还包含解决方案与工作区文件:`AspectCore-Framework.sln`、`NuGet.config`、`LICENSE`、`README.md`。构建通过 dotnet CLI 驱动(见 [本地构建](./building.md)),没有 Cake/PowerShell 构建脚本。 ## 2. `src/` — 源码包 -`src/` 下共 14 个包(不含 `Directory.Build.props`)。按角色可分为三类;各包的职责与依赖方向见 [模块与包结构设计](../architecture/module-design.md)。 +`src/` 下共 15 个包(不含 `Directory.Build.props`)。按角色可分为三类;各包的职责与依赖方向见 [模块与包结构设计](../architecture/module-design.md)。 - 核心:`AspectCore.Abstractions`、`AspectCore.Core`、`AspectCore.Extensions.Reflection` - 编译时引擎:`AspectCore.SourceGenerator`(`netstandard2.0`,供 Roslyn 加载) -- 扩展与集成:`AspectCore.Extensions.DependencyInjection`、`AspectCore.Extensions.Autofac`、`AspectCore.Extensions.Windsor`、`AspectCore.Extensions.LightInject`、`AspectCore.Extensions.Hosting`、`AspectCore.Extensions.AspNetCore`、`AspectCore.Extensions.Configuration`、`AspectCore.Extensions.DataValidation`、`AspectCore.Extensions.DataAnnotations`、`AspectCore.Extensions.AspectScope` +- 扩展与集成:`AspectCore.Extensions.DependencyInjection`、`AspectCore.Extensions.Autofac`、`AspectCore.Extensions.Windsor`、`AspectCore.Extensions.LightInject`、`AspectCore.Extensions.Hosting`、`AspectCore.Extensions.AspNetCore`、`AspectCore.Extensions.Configuration`、`AspectCore.Extensions.DataValidation`、`AspectCore.Extensions.DataAnnotations`、`AspectCore.Extensions.AspectScope`、`AspectCore.Extensions.CastleCompat`(Castle DynamicProxy 兼容层,用于向 AspectCore 渐进迁移) `src/Directory.Build.props` 对所有源码项目启用 .NET 分析器(提示性,不阻断构建)。 @@ -37,6 +37,8 @@ | `AspectCore.E2E.Tests` | 端到端场景测试,用例集中在 `Scenarios/`,公共支撑在 `Fixtures/`(`TestHost.cs`、`TestServices.cs`) | | `AspectCore.Extensions.Reflection.Test` | 反射扩展测试 | | `AspectCore.Extensions.Autofac.Test`、`AspectCore.Extensions.Windsor.Test`、`AspectCore.Extensions.LightInject.Test`、`AspectCore.Extensions.Hosting.Tests`、`AspectCore.Extensions.DependencyInjection.Test`、`AspectCore.Extensions.Configuration.Tests` | 各容器 / 宿主 / 配置的集成测试 | +| `AspectCore.Extensions.CastleCompat.Tests` | Castle DynamicProxy 兼容层的迁移兼容测试(xUnit) | +| `AspectCore.NativeAot.E2E` | NativeAOT 端到端验证项目(可执行程序 `OutputType=Exe`、`PublishAot=true`,非 xUnit 单测),验证 Source Generator 路径在原生二进制下的拦截行为 | `tests/Directory.Build.props` 为所有测试项目统一引入 `coverlet.msbuild` 覆盖率采集。 @@ -58,7 +60,7 @@ ## 6. `build/` — 构建配置 -集中管理版本、签名与公共包属性:`version.props`(产品版本 3.0.0-rc.1)、`common.props`(包元数据 + `LangVersion=13.0`)、`sign.props` + `aspectcore.snk`(强名称签名),以及 Cake 脚本(`index.cake`、`util.cake`、`version.cake`)。详见 [本地构建](./building.md)。 +集中管理版本、签名与公共包属性:`version.props`(产品版本 3.0.0-rc.1)、`common.props`(包元数据 + `LangVersion=13.0`)、`sign.props` + `aspectcore.snk`(强名称签名)。构建由 dotnet CLI 直接驱动、复用这些 props,无 Cake 脚本。详见 [本地构建](./building.md)。 ## 7. `.github/` — CI @@ -71,7 +73,7 @@ CI 详情见 [测试策略](../testing/testing-strategy.md) 与 [贡献指南](. ## 相关文档 -- [模块与包结构设计](../architecture/module-design.md) — 14 个包的职责边界与依赖方向 +- [模块与包结构设计](../architecture/module-design.md) — 15 个包的职责边界与依赖方向 - [本地构建](./building.md) — 还原、编译、目标框架与构建属性 - [测试策略](../testing/testing-strategy.md) — 测试分类与覆盖率门槛 - [文档首页](../README.md) diff --git a/docs/en/README.md b/docs/en/README.md index 10c90696..2d354cac 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -1,6 +1,6 @@ # AspectCore Documentation -> AspectCore is a cross-platform AOP (Aspect-Oriented Programming) framework for .NET Core and .NET Framework, providing dynamic-proxy interception, dependency-injection integration, web application support, data validation, and more. +> AspectCore is a cross-platform AOP (Aspect-Oriented Programming) framework for .NET 6+, providing dynamic-proxy interception, dependency-injection integration, web application support, data validation, and more. This is the complete AspectCore documentation. English lives here; the Chinese version is under [`../`](../README.md)(中文文档见 [`../`](../README.md)). @@ -31,7 +31,7 @@ Feature documentation for day-to-day development. Design documents for contributors and advanced users. - [Overview](./architecture/overview.md) — layering and runtime flows -- [Module & Package Design](./architecture/module-design.md) — responsibilities and dependency direction across the 14 packages +- [Module & Package Design](./architecture/module-design.md) — responsibilities and dependency direction across the 15 packages - [DynamicProxy Runtime Engine](./architecture/dynamic-proxy.md) — runtime proxying based on Reflection.Emit - [Source Generator Compile-Time Engine](./architecture/source-generator.md) — compile-time proxying based on Roslyn - [Engine Comparison & Selection](./architecture/engine-comparison.md) — DynamicProxy vs SourceGenerator vs Auto diff --git a/docs/en/architecture/module-design.md b/docs/en/architecture/module-design.md index 9001c589..b4cceec8 100644 --- a/docs/en/architecture/module-design.md +++ b/docs/en/architecture/module-design.md @@ -30,6 +30,7 @@ Extensions.Reflection ◄──┐ │ ├── Extensions.Windsor ────────────── (Castle.Windsor) ├── Extensions.LightInject ────────── (LightInject) ├── Extensions.Hosting ────────────── (MS.Hosting) -> also depends on DependencyInjection + ├── Extensions.CastleCompat ───────── (Castle.Core) -> also depends on DependencyInjection ├── Extensions.AspectScope ├── Extensions.DataValidation ─────── (depends only on Abstractions + Reflection) ├── Extensions.DataAnnotations ────── -> also depends on DataValidation @@ -113,6 +114,7 @@ These packages weave AspectCore's proxies into different containers; all depend | `Extensions.Windsor` | Castle Windsor | `IWindsorContainer.AddAspectCoreFacility(...)` (in Facility form) | `Castle.Windsor 6.0.0` | | `Extensions.LightInject` | LightInject | `IServiceContainer.RegisterDynamicProxy(...)` (in `Decorate` form) | `LightInject 6.6.4` | | `Extensions.Hosting` | Generic Host | `IHostBuilder.UseServiceContext()` / `UseDynamicProxy()` / `ConfigureDynamicProxy()` | `Microsoft.Extensions.Hosting` (and depends on `DependencyInjection`) | +| `Extensions.CastleCompat` | Castle DynamicProxy migration | A Castle DynamicProxy compatibility shim that provides gradual-migration adaptation from existing Castle code to AspectCore | `Castle.Core 5.2.1` (and depends on `Core` + `DependencyInjection`) | Entry points: `ServiceCollectionExtensions.cs:20`, `Autofac/ContainerBuilderExtensions.cs:16`, `Windsor/FacilityExtensions.cs:11`, `LightInject/ContainerBuilderExtensions.cs:32`, `Hosting/HostBuilderExtensions.cs:12`. @@ -130,7 +132,7 @@ Entry points: `AspectScope/ServiceContainerExtensions.cs:9`, `DataAnnotations/Se ## 7. Tests, Samples, Benchmarks (Non-shipping) -- `tests/`: `AspectCore.Core.Tests` (including the `EngineParity/` dual-engine consistency tests), `AspectCore.E2E.Tests`, various container adapter tests, `AspectCore.Extensions.Reflection.Test`, etc. For details, see [Testing Strategy](../testing/testing-strategy.md). +- `tests/`: `AspectCore.Core.Tests` (including the `EngineParity/` dual-engine consistency tests), `AspectCore.E2E.Tests`, various container adapter tests, `AspectCore.Extensions.Reflection.Test`, `AspectCore.Extensions.CastleCompat.Tests`, and the NativeAOT end-to-end verification project `AspectCore.NativeAot.E2E` (an executable, not xUnit), etc. For details, see [Testing Strategy](../testing/testing-strategy.md). - `sample/`: AspectScope, Autofac, DataAnnotations, and DependencyInjection console samples. - `benchmark/`, `benchmarks/`: benchmark projects for Core and Reflection. diff --git a/docs/en/architecture/overview.md b/docs/en/architecture/overview.md index 953a43f5..06294af4 100644 --- a/docs/en/architecture/overview.md +++ b/docs/en/architecture/overview.md @@ -105,6 +105,6 @@ At **compile time**, the Source Generator scans types marked with `[AspectCoreGe ## 7. Further Reading -- [Module and Package Structure Design](./module-design.md): explains the responsibilities and dependencies of the 14 packages layer by layer. +- [Module and Package Structure Design](./module-design.md): explains the responsibilities and dependencies of the 15 packages layer by layer. - [Engine Comparison and Selection](./engine-comparison.md): when to use which engine, and the behavior of `Strict`/`AllowRuntimeFallback`/`Auto`. - [C# Language Feature Adaptation](./language-features.md): whether each C# version's features need adaptation in AOP Emit. diff --git a/docs/en/development/building.md b/docs/en/development/building.md index 2eb8ed8e..1b85f2b5 100644 --- a/docs/en/development/building.md +++ b/docs/en/development/building.md @@ -64,7 +64,7 @@ Different projects choose target frameworks by purpose; the authoritative source | `AspectCore.Abstractions`, `AspectCore.Core`, `AspectCore.Extensions.Reflection` | `net10.0;net9.0;net8.0;net6.0` | The core packages multi-target, with `net6.0` as the minimum | | `AspectCore.SourceGenerator` | `netstandard2.0` | The compile-time engine must target `netstandard2.0` for Roslyn to load; `LangVersion=latest` | | `AspectCore.Extensions.AspNetCore` | `net10.0;net9.0;net8.0;net6.0` | `net6.0` and above only | -| Container/host and other extension packages | Per each `*.csproj` (mostly `net10.0;net9.0;net8.0;net6.0`) | See [Project structure](./project-structure.md) and [Module and package structure design](../architecture/module-design.md) | +| Container/host and other extension packages | Per each `*.csproj`: most are `net10.0;net9.0;net8.0;net6.0`, except `AspectCore.Extensions.CastleCompat` at `net10.0;net9.0;net8.0` (no `net6.0`) | See [Project structure](./project-structure.md) and [Module and package structure design](../architecture/module-design.md) | | Test projects | Mostly `net10.0;net9.0;net8.0;net6.0` or `net9.0;net8.0;net6.0` | For the specific differences, see [Testing strategy](../testing/testing-strategy.md) | A multi-target build produces one assembly per target framework; therefore, missing a runtime locally will cause the compile or test step for that target framework to fail. @@ -86,5 +86,5 @@ Build configuration is centralized in the `build/` directory and two `Directory. - [Project structure](./project-structure.md) — the layout of the source, test, sample, and benchmark directories - [Contributing guide](./contributing.md) — branch, commit, and PR process - [Running tests](../testing/running-tests.md) — test filtering and coverage collection -- [Module and package structure design](../architecture/module-design.md) — the responsibilities and dependency directions of the 14 packages +- [Module and package structure design](../architecture/module-design.md) — the responsibilities and dependency directions of the 15 packages - [Docs home](../README.md) diff --git a/docs/en/development/project-structure.md b/docs/en/development/project-structure.md index e844f28c..e6d1ed08 100644 --- a/docs/en/development/project-structure.md +++ b/docs/en/development/project-structure.md @@ -6,24 +6,24 @@ This document explains the top-level directory layout of the AspectCore reposito | Directory | Purpose | |------|------| -| `src/` | The 14 publishable source packages (core + extensions + compile-time engine) | -| `tests/` | xUnit test projects: unit, dual-engine parity, E2E, reflection, and per-container integration | +| `src/` | The 15 publishable source packages (core + extensions + compile-time engine) | +| `tests/` | xUnit test projects: unit, dual-engine parity, E2E, reflection, and per-container integration; plus a NativeAOT end-to-end verification project | | `sample/` | Runnable sample projects | | `benchmark/` | The early BenchmarkDotNet benchmark projects (Core, Reflection) | | `benchmarks/` | The new unified benchmark project `AspectCore.Benchmarks` | | `docs/` | This documentation (primarily Chinese; English is under `docs/en/`) | -| `build/` | Version, signing, common package properties, and Cake build scripts | +| `build/` | Version, signing, and common package properties (props) | | `.github/` | CI workflows and the coverage script | -The root also contains the solution and workspace files: `AspectCore-Framework.sln`, `NuGet.config`, `LICENSE`, `README.md`, `build.cake`/`build.ps1`. +The root also contains the solution and workspace files: `AspectCore-Framework.sln`, `NuGet.config`, `LICENSE`, `README.md`. Builds are driven through the dotnet CLI (see [Local build](./building.md)); there are no Cake/PowerShell build scripts. ## 2. `src/` — source packages -There are 14 packages under `src/` (excluding `Directory.Build.props`). They fall into three roles; the responsibilities and dependency directions of each package are in [Module and package structure design](../architecture/module-design.md). +There are 15 packages under `src/` (excluding `Directory.Build.props`). They fall into three roles; the responsibilities and dependency directions of each package are in [Module and package structure design](../architecture/module-design.md). - Core: `AspectCore.Abstractions`, `AspectCore.Core`, `AspectCore.Extensions.Reflection` - Compile-time engine: `AspectCore.SourceGenerator` (`netstandard2.0`, loaded by Roslyn) -- Extensions and integrations: `AspectCore.Extensions.DependencyInjection`, `AspectCore.Extensions.Autofac`, `AspectCore.Extensions.Windsor`, `AspectCore.Extensions.LightInject`, `AspectCore.Extensions.Hosting`, `AspectCore.Extensions.AspNetCore`, `AspectCore.Extensions.Configuration`, `AspectCore.Extensions.DataValidation`, `AspectCore.Extensions.DataAnnotations`, `AspectCore.Extensions.AspectScope` +- Extensions and integrations: `AspectCore.Extensions.DependencyInjection`, `AspectCore.Extensions.Autofac`, `AspectCore.Extensions.Windsor`, `AspectCore.Extensions.LightInject`, `AspectCore.Extensions.Hosting`, `AspectCore.Extensions.AspNetCore`, `AspectCore.Extensions.Configuration`, `AspectCore.Extensions.DataValidation`, `AspectCore.Extensions.DataAnnotations`, `AspectCore.Extensions.AspectScope`, `AspectCore.Extensions.CastleCompat` (a Castle DynamicProxy compatibility shim for gradual migration to AspectCore) `src/Directory.Build.props` enables .NET analyzers for all source projects (advisory, not blocking the build). @@ -37,6 +37,8 @@ Each project under `tests/` corresponds to a category of test target; for test c | `AspectCore.E2E.Tests` | End-to-end scenario tests, with cases concentrated in `Scenarios/` and shared support in `Fixtures/` (`TestHost.cs`, `TestServices.cs`) | | `AspectCore.Extensions.Reflection.Test` | Reflection-extension tests | | `AspectCore.Extensions.Autofac.Test`, `AspectCore.Extensions.Windsor.Test`, `AspectCore.Extensions.LightInject.Test`, `AspectCore.Extensions.Hosting.Tests`, `AspectCore.Extensions.DependencyInjection.Test`, `AspectCore.Extensions.Configuration.Tests` | Integration tests for each container / host / configuration | +| `AspectCore.Extensions.CastleCompat.Tests` | Migration-compatibility tests for the Castle DynamicProxy compatibility shim (xUnit) | +| `AspectCore.NativeAot.E2E` | NativeAOT end-to-end verification project (an executable with `OutputType=Exe`, `PublishAot=true`, not an xUnit test), verifying Source Generator interception behavior in a native binary | `tests/Directory.Build.props` uniformly brings in `coverlet.msbuild` coverage collection for all test projects. @@ -58,7 +60,7 @@ The repository has two benchmark directories: ## 6. `build/` — build configuration -Centralized management of version, signing, and common package properties: `version.props` (product version 3.0.0-rc.1), `common.props` (package metadata + `LangVersion=13.0`), `sign.props` + `aspectcore.snk` (strong-name signing), and the Cake scripts (`index.cake`, `util.cake`, `version.cake`). For details, see [Local build](./building.md). +Centralized management of version, signing, and common package properties: `version.props` (product version 3.0.0-rc.1), `common.props` (package metadata + `LangVersion=13.0`), `sign.props` + `aspectcore.snk` (strong-name signing). Builds are driven directly by the dotnet CLI, reusing these props; there is no Cake script. For details, see [Local build](./building.md). ## 7. `.github/` — CI @@ -71,7 +73,7 @@ For CI details, see [Testing strategy](../testing/testing-strategy.md) and [Cont ## Related docs -- [Module and package structure design](../architecture/module-design.md) — the responsibility boundaries and dependency directions of the 14 packages +- [Module and package structure design](../architecture/module-design.md) — the responsibility boundaries and dependency directions of the 15 packages - [Local build](./building.md) — restore, compile, target frameworks, and build properties - [Testing strategy](../testing/testing-strategy.md) — test categories and coverage thresholds - [Docs home](../README.md) diff --git a/docs/en/getting-started/installation.md b/docs/en/getting-started/installation.md index a69fae43..093c3f95 100644 --- a/docs/en/getting-started/installation.md +++ b/docs/en/getting-started/installation.md @@ -23,6 +23,7 @@ AspectCore is split into multiple NuGet packages by responsibility: the core lib | `AspectCore.Extensions.Configuration` | Injects configuration values from `IConfiguration`: `AddConfigurationInject()`. | | `AspectCore.Extensions.DataValidation` | Data-validation infrastructure (abstractions and interceptors). | | `AspectCore.Extensions.DataAnnotations` | A validation implementation based on `System.ComponentModel.DataAnnotations`: `AddDataAnnotations(...)`. | +| `AspectCore.Extensions.CastleCompat` | A Castle DynamicProxy compatibility shim that provides gradual-migration adaptation from existing Castle code to AspectCore. | ## Compile-time engine @@ -38,6 +39,7 @@ The Source Generator is a separate engine from the runtime DynamicProxy. For the |--------|----------| | Core libraries and most integration packages (`AspectCore.Abstractions`, `AspectCore.Core`, `AspectCore.Extensions.Reflection`, `AspectCore.Extensions.DependencyInjection`, Autofac/Windsor/LightInject/Hosting, Configuration, DataAnnotations, etc.) | `net10.0`, `net9.0`, `net8.0`, `net6.0` | | `AspectCore.Extensions.AspNetCore` | `net10.0`, `net9.0`, `net8.0`, `net6.0` | +| `AspectCore.Extensions.CastleCompat` | `net10.0`, `net9.0`, `net8.0` | | `AspectCore.SourceGenerator` | `netstandard2.0` (analyzer convention) | > The framework lists above come from the `TargetFrameworks` in each package's `.csproj`. The core libraries and integration packages support `net6.0` at minimum; `AspectCore.SourceGenerator`, as a Roslyn analyzer, must target `netstandard2.0` so the compiler can load it. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index c1402460..aab48b8f 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -23,6 +23,7 @@ AspectCore 按职责拆分成多个 NuGet 包:核心库负责 AOP 与容器, | `AspectCore.Extensions.Configuration` | 从 `IConfiguration` 注入配置值:`AddConfigurationInject()`。 | | `AspectCore.Extensions.DataValidation` | 数据校验基础设施(抽象与拦截器)。 | | `AspectCore.Extensions.DataAnnotations` | 基于 `System.ComponentModel.DataAnnotations` 的校验实现:`AddDataAnnotations(...)`。 | +| `AspectCore.Extensions.CastleCompat` | Castle DynamicProxy 兼容层(shim),为存量 Castle 代码提供向 AspectCore 渐进迁移的适配。 | ## 编译期引擎 @@ -38,6 +39,7 @@ Source Generator 是运行时 DynamicProxy 之外的另一套引擎。两者的 |--------|----------| | 核心库与大部分集成包(`AspectCore.Abstractions`、`AspectCore.Core`、`AspectCore.Extensions.Reflection`、`AspectCore.Extensions.DependencyInjection`、Autofac/Windsor/LightInject/Hosting、Configuration、DataAnnotations 等) | `net10.0`、`net9.0`、`net8.0`、`net6.0` | | `AspectCore.Extensions.AspNetCore` | `net10.0`、`net9.0`、`net8.0`、`net6.0` | +| `AspectCore.Extensions.CastleCompat` | `net10.0`、`net9.0`、`net8.0` | | `AspectCore.SourceGenerator` | `netstandard2.0`(analyzer 约定) | > 以上框架列表来自各包 `.csproj` 的 `TargetFrameworks`。核心库与集成包最低支持 `net6.0`;`AspectCore.SourceGenerator` 作为 Roslyn 分析器需以 `netstandard2.0` 供编译器加载。 From b505bb8d72372b792777eae12287a6ce5b01b949 Mon Sep 17 00:00:00 2001 From: Haoyang Liu Date: Sat, 25 Jul 2026 12:41:47 +0800 Subject: [PATCH 3/4] chore: run dotnet format and fix AGENTS.md commit stamp --- AGENTS.md | 2 +- .../AspectCore.Benchmarks/AdditionalBenchmarks.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index dd0b81fc..68620f3e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md — AspectCore-Framework -Project-level AI context for the AspectCore-Framework repository. Generated from the current code tree (commit `d7750bf`, version `3.0.0-rc.1`). Keep this file lean; link to external docs instead of inlining them. +Project-level AI context for the AspectCore-Framework repository. Generated from the current code tree (version `3.0.0-rc.1`). Keep this file lean; link to external docs instead of inlining them. --- diff --git a/benchmarks/AspectCore.Benchmarks/AdditionalBenchmarks.cs b/benchmarks/AspectCore.Benchmarks/AdditionalBenchmarks.cs index 756a4950..9c1ba364 100644 --- a/benchmarks/AspectCore.Benchmarks/AdditionalBenchmarks.cs +++ b/benchmarks/AspectCore.Benchmarks/AdditionalBenchmarks.cs @@ -297,8 +297,14 @@ public override void Setup() _sourceGenProxy = (LargeStructService)SourceGenProxyGen.CreateClassProxy(typeof(LargeStructService), typeof(LargeStructService), Array.Empty()); _data = new LargeStruct { - Field1 = 1, Field2 = 2, Field3 = 3, Field4 = 4, - Field5 = 5, Field6 = 6, Field7 = 7, Field8 = 8 + Field1 = 1, + Field2 = 2, + Field3 = 3, + Field4 = 4, + Field5 = 5, + Field6 = 6, + Field7 = 7, + Field8 = 8 }; } From a1562fe42be21f3c5a70a5ea7077ab345a361461 Mon Sep 17 00:00:00 2001 From: Haoyang Liu Date: Sat, 25 Jul 2026 20:49:13 +0800 Subject: [PATCH 4/4] chore: address PR #403 review feedback (rc.1 docs, sln, whitespace) - docs/release-notes/v3.0.0.md: sync version labels to 3.0.0-rc.1; narrow NativeAOT "zero Emit" claim to the 16 verified scenarios and note remaining AOT warning paths - AspectCore-Framework.sln: add AspectCore.Extensions.CastleCompat and its test project - README.md, docs/architecture/nativeaot-design.md: strip trailing whitespace --- AspectCore-Framework.sln | 30 +++++++++++++++++++++++++++ README.md | 2 +- docs/architecture/nativeaot-design.md | 6 +++--- docs/release-notes/v3.0.0.md | 6 +++--- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/AspectCore-Framework.sln b/AspectCore-Framework.sln index bf07db60..0183e639 100644 --- a/AspectCore-Framework.sln +++ b/AspectCore-Framework.sln @@ -101,6 +101,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspectCore.NativeAot.E2E", "tests\AspectCore.NativeAot.E2E\AspectCore.NativeAot.E2E.csproj", "{A8E6B15A-6F34-44C7-B60F-FA8CD44D2FCC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspectCore.Extensions.CastleCompat", "src\AspectCore.Extensions.CastleCompat\AspectCore.Extensions.CastleCompat.csproj", "{2E607FEC-D838-4F9B-9649-F892CAE4C607}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspectCore.Extensions.CastleCompat.Tests", "tests\AspectCore.Extensions.CastleCompat.Tests\AspectCore.Extensions.CastleCompat.Tests.csproj", "{3B771AA2-C30A-415E-A4E8-D7FAA215A653}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -459,6 +463,30 @@ Global {A8E6B15A-6F34-44C7-B60F-FA8CD44D2FCC}.Release|x64.Build.0 = Release|Any CPU {A8E6B15A-6F34-44C7-B60F-FA8CD44D2FCC}.Release|x86.ActiveCfg = Release|Any CPU {A8E6B15A-6F34-44C7-B60F-FA8CD44D2FCC}.Release|x86.Build.0 = Release|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Debug|x64.ActiveCfg = Debug|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Debug|x64.Build.0 = Debug|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Debug|x86.ActiveCfg = Debug|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Debug|x86.Build.0 = Debug|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Release|Any CPU.Build.0 = Release|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Release|x64.ActiveCfg = Release|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Release|x64.Build.0 = Release|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Release|x86.ActiveCfg = Release|Any CPU + {2E607FEC-D838-4F9B-9649-F892CAE4C607}.Release|x86.Build.0 = Release|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Debug|x64.ActiveCfg = Debug|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Debug|x64.Build.0 = Debug|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Debug|x86.ActiveCfg = Debug|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Debug|x86.Build.0 = Debug|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Release|Any CPU.Build.0 = Release|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Release|x64.ActiveCfg = Release|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Release|x64.Build.0 = Release|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Release|x86.ActiveCfg = Release|Any CPU + {3B771AA2-C30A-415E-A4E8-D7FAA215A653}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -497,6 +525,8 @@ Global {D7C79A32-0351-4E4A-B851-66D52C29CBF1} = {66320409-64EC-F7C5-3DEF-65E7510DAAD1} {72FACDC8-250C-444B-A4C1-F86EDC2B16E1} = {2DFB45E4-F9D9-472C-93E4-224E3C210FCB} {A8E6B15A-6F34-44C7-B60F-FA8CD44D2FCC} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {2E607FEC-D838-4F9B-9649-F892CAE4C607} = {01E9B5AA-9C01-458A-ADF5-8D66101F2FB8} + {3B771AA2-C30A-415E-A4E8-D7FAA215A653} = {2DFB45E4-F9D9-472C-93E4-224E3C210FCB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {71EABBEC-431F-413C-B6CB-CFE9145BCAD4} diff --git a/README.md b/README.md index 02906f20..96e111b5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build](https://github.com/dotnetcore/AspectCore-Framework/actions/workflows/build-ci.yml/badge.svg)](https://github.com/dotnetcore/AspectCore-Framework/actions/workflows/build-ci.yml) [![Member project of .NET Core Community](https://img.shields.io/badge/member%20project%20of-NCC-9e20c9.svg)](https://github.com/dotnetcore) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/dotnetcore/AspectCore/blob/dev/LICENSE) -AspectCore is an Aspect-Oriented Programming based cross platform framework for .NET 6+. +AspectCore is an Aspect-Oriented Programming based cross platform framework for .NET 6+. Core support for aspect-interceptor, dependency injection integration, web applications, data validation, and more. diff --git a/docs/architecture/nativeaot-design.md b/docs/architecture/nativeaot-design.md index 60773f24..64f0e967 100644 --- a/docs/architecture/nativeaot-design.md +++ b/docs/architecture/nativeaot-design.md @@ -1,7 +1,7 @@ # NativeAOT AOP 设计方案 -> 版本:2026-07-20 v4 (final) -> 状态:已批准 +> 版本:2026-07-20 v4 (final) +> 状态:已批准 --- @@ -237,7 +237,7 @@ namespace AspectCore.DynamicProxy /// 1. 调用实现方法获取返回值 /// 2. 等待异步完成(但不提取 Result,保留 Task/ValueTask 对象) /// 3. 赋值给 ReturnValue - /// + /// /// 与 RuntimeAspectContext 的区别仅在实现机制: /// - 方法调度:IAspectInvokeDelegate(编译时委托)替代 MethodReflector(DynamicMethod) /// - 异步等待:NativeAOT 安全实现替代 Expression.Compile() 路径 diff --git a/docs/release-notes/v3.0.0.md b/docs/release-notes/v3.0.0.md index b81a16b5..d8af38f7 100644 --- a/docs/release-notes/v3.0.0.md +++ b/docs/release-notes/v3.0.0.md @@ -2,7 +2,7 @@ AspectCore 是我 2017 年开始做的一个 .NET AOP 框架,挂在 NCC(.NET Core Community)下面,NuGet 累计下载量过百万。2023 年 v2.4.0 之后我没怎么维护了,issue 积了 19 个,最老的挂了五年。其实也不是不想维护,就是工作忙起来之后,一个人的开源项目很容易就放下了,放下越久越不想捡。 -今年我把它捡了回来,但做法变了。我拉了两个 AI coding agent 进团队,一个写代码,一个审代码,我自己只管定方向。十天后发了 v3.0.0-beta.1。听起来像噱头,但从我的经验看,这次确实跑通了。下面把过程记下来,也包括中间踩的坑和还没解决的问题。 +今年我把它捡了回来,但做法变了。我拉了两个 AI coding agent 进团队,一个写代码,一个审代码,我自己只管定方向。十天后发了 v3.0.0-rc.1。听起来像噱头,但从我的经验看,这次确实跑通了。下面把过程记下来,也包括中间踩的坑和还没解决的问题。 --- @@ -50,9 +50,9 @@ NativeAOT 是整个 v3 最难的部分,设计方案写了四版才通过。 ## 最终做了什么 -十天,从 v2.4.0 到 v3.0.0-beta.1,ROADMAP 短中期全部清完。 +十天,从 v2.4.0 到 v3.0.0-rc.1,ROADMAP 短中期全部清完。 -NativeAOT 支持是主线。Source Generator 引擎在编译时为每个被拦截方法生成强类型调度委托,运行时直接调用,零 Emit、零 Expression.Compile。16 个场景在原生二进制里验证通过,冷启动 18ms。Castle DynamicProxy 不支持,Metalama 是闭源的编译时方案但不是代理模式,开源的运行时+编译时双引擎 AOP 框架里,这应该是第一个。当然这个判断基于我目前的了解,如果有遗漏欢迎指出。 +NativeAOT 支持是主线。Source Generator 引擎在编译时为每个被拦截方法生成强类型调度委托,运行时直接调用,在已验证的 16 个场景中避免了 Emit 与 Expression.Compile;但部分动态代码路径(如 ProxyTypeCompiler、ILEmitVisitor、MethodReflector 及泛型回退的 MakeGenericMethod)仍会在 NativeAOT 发布时触发分析警告。这 16 个场景在原生二进制里验证通过,冷启动 18ms。Castle DynamicProxy 不支持,Metalama 是闭源的编译时方案但不是代理模式,开源的运行时+编译时双引擎 AOP 框架里,这应该是第一个。当然这个判断基于我目前的了解,如果有遗漏欢迎指出。 性能这边有个剧情反转。一开始 Source Generator 引擎比 DynamicProxy 慢 38%,inline activation 每次分配 object[] 和各种 Tuple,把编译时委托的优势全吃了。三轮优化后翻过来了:SG 比 DP 快 49%,内存分配少 69%。核心手段是条件编译,net8.0+ 用 ObjectPool 和 FrozenDictionary,net6.0 走 fallback,不破坏兼容。