应文浩wenhao.ying@xiaobao100.com e25ac591a7 init easy-code
2026-04-06 07:24:24 +08:00

11 KiB
Raw Blame History

.NET 10 平台介绍

文档版本: 1.0
日期: 2026-04-05
所属部分: 第一部分 — 参考文档
单一职责: 说明 .NET 10 平台中与本项目直接相关的特性,不涉及具体映射细节


目录


1. AOT 编译与单文件发布

设计意图

原始项目通过 Bun 将所有 TypeScript 打包为单一可执行二进制,做到零依赖安装。.NET 10 的 Native AOTAhead-of-Time编译提供了对等的能力并在启动速度和内存占用上更有优势。

PublishSingleFile

PublishSingleFile 将运行时和应用代码打包为一个文件,发布时无需 .NET 运行时预装。

<!-- 项目文件中的 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 类型天然不可变,自动生成 EqualsGetHashCodeToString 和解构支持,适合表达状态快照和配置对象。

// 不可变配置模型
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 处理。

// 替代 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 警告更明确。

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] 特性,编译器在构建时生成所有序列化代码,不依赖运行时反射。

// 声明需要序列化的类型
[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 可以零拷贝解析,不需要先将整个响应体读入内存。

// 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 构建

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 中运行后台循环任务的标准模式,替代原始项目中的自定义异步循环。

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

基本用法

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;
    }
}

卸载流程

// 加载插件
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 基本模式

// 创建无界通道
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);
}

有界通道与背压

// 有界通道:满了就等待(背压)
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.* 断言库