黄中银 32db87f579 Nuget升级脚本 hace 9 horas
..
Apq.Cfg.SourceGenerator.csproj 32db87f579 Nuget升级脚本 hace 9 horas
CfgSectionAttribute.cs 06a27b2e6a AOT兼容性增强 hace 23 horas
CfgSectionGenerator.cs 06a27b2e6a AOT兼容性增强 hace 23 horas
CodeEmitter.cs 06a27b2e6a AOT兼容性增强 hace 23 horas
Directory.Build.props 06a27b2e6a AOT兼容性增强 hace 23 horas
IsExternalInit.cs 06a27b2e6a AOT兼容性增强 hace 23 horas
README.md 06a27b2e6a AOT兼容性增强 hace 23 horas

README.md

Apq.Cfg.SourceGenerator

基于 Roslyn 的源生成器,为 Apq.Cfg 配置类自动生成零反射的绑定代码,支持 Native AOT。

特性

  • 零反射绑定 - 编译时生成强类型绑定代码,无运行时反射开销
  • Native AOT 兼容 - 完全支持 .NET Native AOT 发布
  • 增量生成 - 使用 Roslyn 增量源生成器,仅在代码变更时重新生成
  • 丰富类型支持 - 支持简单类型、嵌套对象、数组、List、Dictionary、HashSet 等

安装

dotnet add package Apq.Cfg.SourceGenerator

使用方法

1. 定义配置类

使用 [CfgSection] 特性标记配置类,类必须是 partial 的:

using Apq.Cfg;

[CfgSection("AppSettings")]
public partial class AppConfig
{
    public string? Name { get; set; }
    public int Port { get; set; }
    public DatabaseConfig? Database { get; set; }
}

[CfgSection]
public partial class DatabaseConfig
{
    public string? ConnectionString { get; set; }
    public int Timeout { get; set; } = 30;
}

2. 使用生成的绑定方法

源生成器会自动为每个配置类生成 BindFromBindTo 静态方法:

// 从配置节创建新实例
var config = AppConfig.BindFrom(cfgRoot.GetSection("AppSettings"));

// 或绑定到已有实例
var existingConfig = new AppConfig();
AppConfig.BindTo(cfgRoot.GetSection("AppSettings"), existingConfig);

3. 使用扩展方法(可选)

如果在 [CfgSection] 中指定了 SectionPath,还会生成 ICfgRoot 扩展方法:

// 直接从 ICfgRoot 获取配置
var config = cfgRoot.GetAppConfig();

支持的类型

简单类型

  • string, int, long, short, byte, sbyte
  • uint, ulong, ushort
  • float, double, decimal
  • bool, char
  • DateTime, DateTimeOffset, TimeSpan
  • Guid, Uri
  • DateOnly, TimeOnly (.NET 6+)
  • 枚举类型

集合类型

  • T[] (数组)
  • List<T>
  • HashSet<T>
  • Dictionary<TKey, TValue>

复杂类型

  • 嵌套的配置类(需要同样标记 [CfgSection]

生成的代码示例

对于上面的 AppConfig 类,源生成器会生成类似以下的代码:

partial class AppConfig
{
    public static AppConfig BindFrom(ICfgSection section)
    {
        if (section == null) throw new ArgumentNullException(nameof(section));
        var result = new AppConfig();
        BindTo(section, result);
        return result;
    }

    public static void BindTo(ICfgSection section, AppConfig target)
    {
        if (section == null) throw new ArgumentNullException(nameof(section));
        if (target == null) throw new ArgumentNullException(nameof(target));

        // Name: string?
        {
            var __value = section.Get("Name");
            if (__value != null)
            {
                target.Name = __value;
            }
        }

        // Port: int
        {
            var __value = section.Get("Port");
            if (__value != null)
            {
                var __converted = int.TryParse(__value, out var __intVal) ? __intVal : (int?)null;
                if (__converted != null) target.Port = __converted.Value;
            }
        }

        // Database: DatabaseConfig? (复杂对象)
        {
            var __childSection = section.GetSection("Database");
            var __childKeys = __childSection.GetChildKeys().ToList();
            if (__childKeys.Count > 0)
            {
                target.Database = DatabaseConfig.BindFrom(__childSection);
            }
        }
    }
}

查看生成的代码

在项目文件中添加以下配置可以保留生成的源代码:

<PropertyGroup>
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

生成的文件将位于 obj/GeneratedFiles/ 目录下。

与 Apq.Cfg 配合使用

using Apq.Cfg;

// 使用 CfgBuilder 创建配置根
var cfgRoot = CfgBuilder.Create()
    .AddJson("config.json")
    .AddIni("config.ini")
    .AddEnvironmentVariables("APP_")
    .Build();

// 使用源生成器绑定配置
var appConfig = AppConfig.BindFrom(cfgRoot.GetSection("App"));

Console.WriteLine($"App: {appConfig.Name}");
Console.WriteLine($"Port: {appConfig.Port}");
Console.WriteLine($"Database: {appConfig.Database?.ConnectionString}");

要求

  • .NET 6.0 或更高版本
  • C# 9.0 或更高版本(支持 partial 类)

许可证

MIT License