329 lines
11 KiB
Markdown
329 lines
11 KiB
Markdown
# .NET 10 平台介绍
|
||
|
||
> **文档版本**: 1.0
|
||
> **日期**: 2026-04-05
|
||
> **所属部分**: 第一部分 — 参考文档
|
||
> **单一职责**: 说明 .NET 10 平台中与本项目直接相关的特性,不涉及具体映射细节
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
- [1. AOT 编译与单文件发布](#1-aot-编译与单文件发布)
|
||
- [2. C# 13 语言特性](#2-c-13-语言特性)
|
||
- [3. System.Text.Json 与 AOT 友好序列化](#3-systemtextjson-与-aot-友好序列化)
|
||
- [4. Microsoft.Extensions.Hosting](#4-microsoftextensionshosting)
|
||
- [5. AssemblyLoadContext 插件隔离](#5-assemblyloadcontext-插件隔离)
|
||
- [6. System.Threading.Channels 异步任务管理](#6-systemthreadingchannels-异步任务管理)
|
||
- [7. 关键 NuGet 包清单](#7-关键-nuget-包清单)
|
||
|
||
---
|
||
|
||
## 1. AOT 编译与单文件发布
|
||
|
||
### 设计意图
|
||
|
||
原始项目通过 Bun 将所有 TypeScript 打包为单一可执行二进制,做到零依赖安装。.NET 10 的 Native AOT(Ahead-of-Time)编译提供了对等的能力,并在启动速度和内存占用上更有优势。
|
||
|
||
### PublishSingleFile
|
||
|
||
`PublishSingleFile` 将运行时和应用代码打包为一个文件,发布时无需 .NET 运行时预装。
|
||
|
||
```xml
|
||
<!-- 项目文件中的 AOT + 单文件配置 -->
|
||
<PropertyGroup>
|
||
<PublishSingleFile>true</PublishSingleFile>
|
||
<PublishTrimmed>true</PublishTrimmed>
|
||
<AotPublish>true</AotPublish>
|
||
<RuntimeIdentifiers>osx-arm64;osx-x64;linux-x64;linux-arm64;win-x64</RuntimeIdentifiers>
|
||
</PropertyGroup>
|
||
```
|
||
|
||
### Native AOT 的约束
|
||
|
||
AOT 编译会在发布时静态分析所有代码路径,任何运行时反射都可能被裁剪(trim)。这意味着:
|
||
|
||
- `System.Text.Json` 必须使用 Source Generator 模式,不能依赖运行时反射序列化
|
||
- 插件系统无法直接 AOT,主程序集使用 AOT,插件程序集在 `AssemblyLoadContext` 中通过 JIT 加载
|
||
- 需要为 `[DynamicallyAccessedMembers]` 标注所有通过反射访问的类型
|
||
|
||
### 启动性能对比
|
||
|
||
| 方式 | 冷启动时间(参考值) |
|
||
|------|-----------------|
|
||
| Bun 打包 JS | ~80ms |
|
||
| .NET 10 AOT | ~15-30ms |
|
||
| .NET 10 JIT(无 AOT) | ~150-300ms |
|
||
|
||
---
|
||
|
||
## 2. C# 13 语言特性
|
||
|
||
### 设计意图
|
||
|
||
TypeScript 的类型系统依赖接口、联合类型和 Zod 运行时校验。C# 13 提供了更强的编译时保证,用 record 类型替代不可变 DTO,用模式匹配替代联合类型的分支处理。
|
||
|
||
### Record 类型
|
||
|
||
Record 类型天然不可变,自动生成 `Equals`、`GetHashCode`、`ToString` 和解构支持,适合表达状态快照和配置对象。
|
||
|
||
```csharp
|
||
// 不可变配置模型
|
||
public record AppSettings(
|
||
string Model,
|
||
string Theme,
|
||
bool VerboseMode,
|
||
IReadOnlyList<string> AllowedDirectories
|
||
);
|
||
|
||
// 带 required 修饰符的 init-only 属性
|
||
public record FileReadInput
|
||
{
|
||
public required string FilePath { get; init; }
|
||
public int? Offset { get; init; }
|
||
public int? Limit { get; init; }
|
||
}
|
||
```
|
||
|
||
### 模式匹配
|
||
|
||
C# 13 的模式匹配可以替代 TypeScript 中对联合类型的 `switch` + `instanceof` 处理。
|
||
|
||
```csharp
|
||
// 替代 TS 的 if (msg.type === 'tool_use') { ... }
|
||
var result = message switch
|
||
{
|
||
ToolUseMessage toolUse => HandleToolUse(toolUse),
|
||
AssistantMessage assistant => HandleAssistant(assistant),
|
||
ErrorMessage { Code: >= 500 } serverError => HandleServerError(serverError),
|
||
_ => HandleUnknown(message)
|
||
};
|
||
```
|
||
|
||
### required init 属性
|
||
|
||
`required` 关键字在编译时强制调用方必须初始化某个属性,比构造函数参数更灵活,比 Nullable 警告更明确。
|
||
|
||
```csharp
|
||
public class ToolExecutionContext
|
||
{
|
||
public required string SessionId { get; init; }
|
||
public required IPermissionEngine PermissionEngine { get; init; }
|
||
public CancellationToken CancellationToken { get; init; }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. System.Text.Json 与 AOT 友好序列化
|
||
|
||
### 设计意图
|
||
|
||
原始项目直接使用 `JSON.parse`/`JSON.stringify`,无类型安全。`System.Text.Json` 提供高性能序列化,配合 Source Generator 完全绕过运行时反射,AOT 环境下也可正常工作。
|
||
|
||
### Source Generator 模式
|
||
|
||
通过 `[JsonSerializable]` 特性,编译器在构建时生成所有序列化代码,不依赖运行时反射。
|
||
|
||
```csharp
|
||
// 声明需要序列化的类型
|
||
[JsonSerializable(typeof(ApiRequest))]
|
||
[JsonSerializable(typeof(ApiResponse))]
|
||
[JsonSerializable(typeof(McpToolCall))]
|
||
[JsonSerializable(typeof(List<ToolDefinition>))]
|
||
internal partial class AppJsonContext : JsonSerializerContext { }
|
||
|
||
// 使用时传入 context
|
||
var json = JsonSerializer.Serialize(request, AppJsonContext.Default.ApiRequest);
|
||
var response = JsonSerializer.Deserialize(json, AppJsonContext.Default.ApiResponse);
|
||
```
|
||
|
||
### 流式 JSON 处理
|
||
|
||
MCP 和 API 响应都基于流式 JSON,使用 `Utf8JsonReader` 可以零拷贝解析,不需要先将整个响应体读入内存。
|
||
|
||
```csharp
|
||
// SSE 流式解析示例
|
||
await foreach (var line in sseReader.ReadLinesAsync(ct))
|
||
{
|
||
if (!line.StartsWith("data: ")) continue;
|
||
var data = line["data: ".Length..];
|
||
if (data == "[DONE]") break;
|
||
|
||
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(data));
|
||
var chunk = JsonSerializer.Deserialize(ref reader, AppJsonContext.Default.StreamChunk);
|
||
yield return chunk;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Microsoft.Extensions.Hosting
|
||
|
||
### 设计意图
|
||
|
||
原始项目没有显式的 DI 容器,各模块通过模块级变量和闭包共享状态。.NET 的 `Microsoft.Extensions.Hosting` 提供了标准化的应用生命周期管理,包括 DI 注册、配置系统和 `BackgroundService`。
|
||
|
||
### Host 构建
|
||
|
||
```csharp
|
||
var host = Host.CreateDefaultBuilder(args)
|
||
.ConfigureAppConfiguration((ctx, cfg) =>
|
||
{
|
||
cfg.AddJsonFile("appsettings.json", optional: true);
|
||
cfg.AddEnvironmentVariables("FREECODE_");
|
||
cfg.AddCommandLine(args);
|
||
})
|
||
.ConfigureServices((ctx, services) =>
|
||
{
|
||
services.AddCoreServices()
|
||
.AddEngine()
|
||
.AddTools()
|
||
.AddCommands()
|
||
.AddApiProviders()
|
||
.AddMcp()
|
||
.AddLsp();
|
||
})
|
||
.ConfigureLogging(logging =>
|
||
{
|
||
logging.AddConsole();
|
||
logging.SetMinimumLevel(LogLevel.Warning); // 终端应用默认不打印 Info 日志
|
||
})
|
||
.Build();
|
||
```
|
||
|
||
### BackgroundService
|
||
|
||
`BackgroundService` 是 .NET 中运行后台循环任务的标准模式,替代原始项目中的自定义异步循环。
|
||
|
||
```csharp
|
||
public class TaskSchedulerService : BackgroundService
|
||
{
|
||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||
{
|
||
await foreach (var task in _taskChannel.Reader.ReadAllAsync(stoppingToken))
|
||
{
|
||
_ = Task.Run(() => ExecuteTaskAsync(task, stoppingToken), stoppingToken);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. AssemblyLoadContext 插件隔离
|
||
|
||
### 设计意图
|
||
|
||
原始项目使用 `动态 import()` 加载插件,JavaScript 的模块系统天然支持按需加载。.NET 中通过 `AssemblyLoadContext` 实现对等的运行时程序集隔离,并支持卸载(collectible context)。
|
||
|
||
### 基本用法
|
||
|
||
```csharp
|
||
public class PluginLoadContext : AssemblyLoadContext
|
||
{
|
||
private readonly AssemblyDependencyResolver _resolver;
|
||
|
||
public PluginLoadContext(string pluginPath)
|
||
: base(isCollectible: true) // 可卸载
|
||
{
|
||
_resolver = new AssemblyDependencyResolver(pluginPath);
|
||
}
|
||
|
||
protected override Assembly? Load(AssemblyName assemblyName)
|
||
{
|
||
// 优先从插件目录解析,避免与主程序集冲突
|
||
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||
return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 卸载流程
|
||
|
||
```csharp
|
||
// 加载插件
|
||
var context = new PluginLoadContext(pluginPath);
|
||
var assembly = context.LoadFromAssemblyPath(pluginPath);
|
||
var plugin = (IPlugin)Activator.CreateInstance(assembly.GetType("MyPlugin")!)!;
|
||
|
||
// 卸载插件(释放所有强引用后 GC 自动回收)
|
||
context.Unload();
|
||
```
|
||
|
||
### AOT 注意事项
|
||
|
||
主程序集使用 AOT 编译,但插件程序集在运行时通过 `AssemblyLoadContext` 以 JIT 模式加载。这是混合部署模式:主入口 AOT 启动快、内存小,插件保持 JIT 的灵活性。
|
||
|
||
---
|
||
|
||
## 6. System.Threading.Channels 异步任务管理
|
||
|
||
### 设计意图
|
||
|
||
原始项目的后台任务通过自定义队列和 Promise 链管理,存在竞态风险。`System.Threading.Channels` 是 .NET 内建的高性能生产者-消费者通道,线程安全,背压(backpressure)支持完善。
|
||
|
||
### Channel 基本模式
|
||
|
||
```csharp
|
||
// 创建无界通道
|
||
var channel = Channel.CreateUnbounded<BackgroundTask>(new UnboundedChannelOptions
|
||
{
|
||
SingleWriter = false,
|
||
SingleReader = true // 单消费者简化并发模型
|
||
});
|
||
|
||
// 生产者(任意线程)
|
||
await channel.Writer.WriteAsync(new LocalShellTask { Command = "npm test" }, ct);
|
||
|
||
// 消费者(BackgroundService 中)
|
||
await foreach (var task in channel.Reader.ReadAllAsync(ct))
|
||
{
|
||
await ProcessTaskAsync(task, ct);
|
||
}
|
||
```
|
||
|
||
### 有界通道与背压
|
||
|
||
```csharp
|
||
// 有界通道:满了就等待(背压)
|
||
var bounded = Channel.CreateBounded<ApiRequest>(new BoundedChannelOptions(capacity: 100)
|
||
{
|
||
FullMode = BoundedChannelFullMode.Wait
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 关键 NuGet 包清单
|
||
|
||
以下是本项目直接依赖的 NuGet 包,版本号为设计时目标版本。
|
||
|
||
| 包名 | 版本 | 用途 |
|
||
|------|------|------|
|
||
| `Terminal.Gui` | 2.* | 终端 TUI 框架,替代 React+Ink |
|
||
| `Spectre.Console` | 0.* | 富文本输出:表格、进度条、面板 |
|
||
| `System.CommandLine` | 2.* | CLI 参数解析,替代 Commander.js |
|
||
| `FluentValidation` | 12.* | POCO 对象校验,替代 Zod |
|
||
| `JsonSchema.Net` | 7.* | JSON Schema 生成与校验 |
|
||
| `Refit` | 8.* | 强类型 HTTP 客户端 |
|
||
| `Polly` | 8.* | 重试、熔断、超时策略 |
|
||
| `StreamJsonRpc` | 2.* | JSON-RPC 2.0 实现,用于 MCP 协议 |
|
||
| `OmniSharp.Extensions.LanguageServer.Client` | 0.* | LSP 客户端 |
|
||
| `Grpc.Net.Client` | 2.* | gRPC 客户端(部分 API 提供商) |
|
||
| `Microsoft.Identity.Client` (MSAL) | 4.* | OAuth/OIDC 认证 |
|
||
| `IdentityModel.OidcClient` | 6.* | OIDC 客户端流程 |
|
||
| `AWSSDK.BedrockRuntime` | 4.* | AWS Bedrock 流式 API |
|
||
| `Google.Apis.Auth` | 1.* | Google Cloud 认证(Vertex AI) |
|
||
| `Microsoft.AspNetCore.SignalR.Client` | 10.* | WebSocket 高层抽象 |
|
||
| `Markdig` | 0.* | Markdown 渲染 |
|
||
| `DiffPlex` | 1.* | diff 计算与展示 |
|
||
| `SixLabors.ImageSharp` | 3.* | 图片处理 |
|
||
| `PdfPig` | 0.* | PDF 文本提取 |
|
||
| `YamlDotNet` | 16.* | YAML 解析 |
|
||
| `Microsoft.Extensions.Hosting` | 10.* | DI + 生命周期管理 |
|
||
| `System.Threading.Channels` | 10.* | 生产者-消费者通道 |
|
||
| `xunit` | 2.* | 单元测试框架 |
|
||
| `NSubstitute` | 5.* | Mock 框架 |
|
||
| `FluentAssertions` | 7.* | 断言库 |
|