From 48fd8a22bb5891062e9b4cd2ebc983ff697db136 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Fri, 3 Apr 2026 09:25:06 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(config):=20=E6=B7=BB=E5=8A=A0AI-First?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=B3=BB=E7=BB=9F=E5=8F=8A=E6=BA=90=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现YAML配置文件加载和JSON Schema校验功能 - 提供Source Generator自动生成配置类型和表包装类 - 添加VS Code插件支持配置浏览和表单编辑 - 支持跨表引用校验和开发期热重载功能 - 生成强类型的配置访问辅助方法和注册绑定 - 实现嵌套对象和对象数组的类型安全访问 --- .../SchemaConfigGeneratorSnapshotTests.cs | 30 +++- .../MonsterConfigBindings.g.txt | 89 +++++++++ .../Config/SchemaConfigGenerator.cs | 170 ++++++++++++++++++ docs/zh-CN/game/config-system.md | 29 +-- 4 files changed, 307 insertions(+), 11 deletions(-) create mode 100644 GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/MonsterConfigBindings.g.txt diff --git a/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorSnapshotTests.cs b/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorSnapshotTests.cs index 561555d7..b02b1307 100644 --- a/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorSnapshotTests.cs +++ b/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorSnapshotTests.cs @@ -9,7 +9,7 @@ namespace GFramework.SourceGenerators.Tests.Config; public class SchemaConfigGeneratorSnapshotTests { /// - /// 验证一个最小 monster schema 能生成配置类型和表包装。 + /// 验证一个最小 monster schema 能生成配置类型、表包装和注册辅助。 /// [Test] public async Task Snapshot_SchemaConfigGenerator() @@ -35,6 +35,32 @@ public class SchemaConfigGeneratorSnapshotTests bool ContainsKey(TKey key); IReadOnlyCollection All(); } + + public interface IConfigRegistry + { + IConfigTable GetTable(string name) + where TKey : notnull; + + bool TryGetTable(string name, out IConfigTable? table) + where TKey : notnull; + } + } + + namespace GFramework.Game.Config + { + public sealed class YamlConfigLoader + { + public YamlConfigLoader RegisterTable( + string tableName, + string relativePath, + string schemaRelativePath, + Func keySelector, + IEqualityComparer? comparer = null) + where TKey : notnull + { + return this; + } + } } """; @@ -130,6 +156,8 @@ public class SchemaConfigGeneratorSnapshotTests await AssertSnapshotAsync(generatedSources, snapshotFolder, "MonsterConfig.g.cs", "MonsterConfig.g.txt"); await AssertSnapshotAsync(generatedSources, snapshotFolder, "MonsterTable.g.cs", "MonsterTable.g.txt"); + await AssertSnapshotAsync(generatedSources, snapshotFolder, "MonsterConfigBindings.g.cs", + "MonsterConfigBindings.g.txt"); } /// diff --git a/GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/MonsterConfigBindings.g.txt b/GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/MonsterConfigBindings.g.txt new file mode 100644 index 00000000..9893f455 --- /dev/null +++ b/GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/MonsterConfigBindings.g.txt @@ -0,0 +1,89 @@ +// +#nullable enable + +namespace GFramework.Game.Config.Generated; + +/// +/// Auto-generated registration and lookup helpers for schema file 'monster.schema.json'. +/// The helper centralizes table naming, config directory, schema path, and strong-typed registry access so consumer projects do not need to duplicate the same conventions. +/// +public static class MonsterConfigBindings +{ + /// + /// Gets the runtime registration name of the generated config table. + /// + public const string TableName = "monster"; + + /// + /// Gets the config directory path expected by the generated registration helper. + /// + public const string ConfigRelativePath = "monster"; + + /// + /// Gets the schema file path expected by the generated registration helper. + /// + public const string SchemaRelativePath = "schemas/monster.schema.json"; + + /// + /// Registers the generated config table using the schema-derived runtime conventions. + /// + /// The target YAML config loader. + /// Optional key comparer for the generated table registration. + /// The same loader instance so registration can keep chaining. + public static global::GFramework.Game.Config.YamlConfigLoader RegisterMonsterTable( + this global::GFramework.Game.Config.YamlConfigLoader loader, + global::System.Collections.Generic.IEqualityComparer? comparer = null) + { + if (loader is null) + { + throw new global::System.ArgumentNullException(nameof(loader)); + } + + return loader.RegisterTable( + TableName, + ConfigRelativePath, + SchemaRelativePath, + static config => config.Id, + comparer); + } + + /// + /// Gets the generated config table wrapper from the registry. + /// + /// The source config registry. + /// The generated strong-typed table wrapper. + /// When is null. + public static MonsterTable GetMonsterTable(this global::GFramework.Game.Abstractions.Config.IConfigRegistry registry) + { + if (registry is null) + { + throw new global::System.ArgumentNullException(nameof(registry)); + } + + return new MonsterTable(registry.GetTable(TableName)); + } + + /// + /// Tries to get the generated config table wrapper from the registry. + /// + /// The source config registry. + /// The generated strong-typed table wrapper when lookup succeeds; otherwise null. + /// True when the generated table is registered and type-compatible; otherwise false. + /// When is null. + public static bool TryGetMonsterTable(this global::GFramework.Game.Abstractions.Config.IConfigRegistry registry, out MonsterTable? table) + { + if (registry is null) + { + throw new global::System.ArgumentNullException(nameof(registry)); + } + + if (registry.TryGetTable(TableName, out var innerTable) && innerTable is not null) + { + table = new MonsterTable(innerTable); + return true; + } + + table = null; + return false; + } +} diff --git a/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs b/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs index 663f5313..6428f624 100644 --- a/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs +++ b/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs @@ -41,6 +41,9 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator productionContext.AddSource( $"{result.Schema.TableName}.g.cs", SourceText.From(GenerateTableClass(result.Schema), Encoding.UTF8)); + productionContext.AddSource( + $"{result.Schema.EntityName}ConfigBindings.g.cs", + SourceText.From(GenerateBindingsClass(result.Schema), Encoding.UTF8)); }); } @@ -128,12 +131,18 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator idProperty.TypeSpec.SchemaType)); } + var schemaBaseName = GetSchemaBaseName(file.Path); var schema = new SchemaFileSpec( Path.GetFileName(file.Path), + entityName, schemaObject.ClassName, $"{entityName}Table", GeneratedNamespace, idProperty.TypeSpec.ClrType.TrimEnd('?'), + idProperty.PropertyName, + schemaBaseName, + schemaBaseName, + GetSchemaRelativePath(file.Path), TryGetMetadataString(root, "title"), TryGetMetadataString(root, "description"), schemaObject); @@ -607,6 +616,131 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator return builder.ToString().TrimEnd(); } + /// + /// 生成运行时注册与访问辅助源码。 + /// 该辅助类型把 schema 命名约定、配置目录和 schema 相对路径固化为生成代码, + /// 让消费端无需重复手写字符串常量和主键提取逻辑。 + /// + /// 已解析的 schema 模型。 + /// 辅助类型源码。 + private static string GenerateBindingsClass(SchemaFileSpec schema) + { + var registerMethodName = $"Register{schema.EntityName}Table"; + var getMethodName = $"Get{schema.EntityName}Table"; + var tryGetMethodName = $"TryGet{schema.EntityName}Table"; + var bindingsClassName = $"{schema.EntityName}ConfigBindings"; + + var builder = new StringBuilder(); + builder.AppendLine("// "); + builder.AppendLine("#nullable enable"); + builder.AppendLine(); + builder.AppendLine($"namespace {schema.Namespace};"); + builder.AppendLine(); + builder.AppendLine("/// "); + builder.AppendLine( + $"/// Auto-generated registration and lookup helpers for schema file '{schema.FileName}'."); + builder.AppendLine( + "/// The helper centralizes table naming, config directory, schema path, and strong-typed registry access so consumer projects do not need to duplicate the same conventions."); + builder.AppendLine("/// "); + builder.AppendLine($"public static class {bindingsClassName}"); + builder.AppendLine("{"); + builder.AppendLine(" /// "); + builder.AppendLine(" /// Gets the runtime registration name of the generated config table."); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public const string TableName = {SymbolDisplay.FormatLiteral(schema.TableRegistrationName, true)};"); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine(" /// Gets the config directory path expected by the generated registration helper."); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public const string ConfigRelativePath = {SymbolDisplay.FormatLiteral(schema.ConfigRelativePath, true)};"); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine(" /// Gets the schema file path expected by the generated registration helper."); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public const string SchemaRelativePath = {SymbolDisplay.FormatLiteral(schema.SchemaRelativePath, true)};"); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine( + " /// Registers the generated config table using the schema-derived runtime conventions."); + builder.AppendLine(" /// "); + builder.AppendLine(" /// The target YAML config loader."); + builder.AppendLine( + " /// Optional key comparer for the generated table registration."); + builder.AppendLine(" /// The same loader instance so registration can keep chaining."); + builder.AppendLine( + $" public static global::GFramework.Game.Config.YamlConfigLoader {registerMethodName}("); + builder.AppendLine(" this global::GFramework.Game.Config.YamlConfigLoader loader,"); + builder.AppendLine( + $" global::System.Collections.Generic.IEqualityComparer<{schema.KeyClrType}>? comparer = null)"); + builder.AppendLine(" {"); + builder.AppendLine(" if (loader is null)"); + builder.AppendLine(" {"); + builder.AppendLine(" throw new global::System.ArgumentNullException(nameof(loader));"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine( + $" return loader.RegisterTable<{schema.KeyClrType}, {schema.ClassName}>("); + builder.AppendLine(" TableName,"); + builder.AppendLine(" ConfigRelativePath,"); + builder.AppendLine(" SchemaRelativePath,"); + builder.AppendLine($" static config => config.{schema.KeyPropertyName},"); + builder.AppendLine(" comparer);"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine(" /// Gets the generated config table wrapper from the registry."); + builder.AppendLine(" /// "); + builder.AppendLine(" /// The source config registry."); + builder.AppendLine(" /// The generated strong-typed table wrapper."); + builder.AppendLine( + " /// When is null."); + builder.AppendLine( + $" public static {schema.TableName} {getMethodName}(this global::GFramework.Game.Abstractions.Config.IConfigRegistry registry)"); + builder.AppendLine(" {"); + builder.AppendLine(" if (registry is null)"); + builder.AppendLine(" {"); + builder.AppendLine(" throw new global::System.ArgumentNullException(nameof(registry));"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine( + $" return new {schema.TableName}(registry.GetTable<{schema.KeyClrType}, {schema.ClassName}>(TableName));"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine(" /// Tries to get the generated config table wrapper from the registry."); + builder.AppendLine(" /// "); + builder.AppendLine(" /// The source config registry."); + builder.AppendLine( + " /// The generated strong-typed table wrapper when lookup succeeds; otherwise null."); + builder.AppendLine( + " /// True when the generated table is registered and type-compatible; otherwise false."); + builder.AppendLine( + " /// When is null."); + builder.AppendLine( + $" public static bool {tryGetMethodName}(this global::GFramework.Game.Abstractions.Config.IConfigRegistry registry, out {schema.TableName}? table)"); + builder.AppendLine(" {"); + builder.AppendLine(" if (registry is null)"); + builder.AppendLine(" {"); + builder.AppendLine(" throw new global::System.ArgumentNullException(nameof(registry));"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine( + $" if (registry.TryGetTable<{schema.KeyClrType}, {schema.ClassName}>(TableName, out var innerTable) && innerTable is not null)"); + builder.AppendLine(" {"); + builder.AppendLine($" table = new {schema.TableName}(innerTable);"); + builder.AppendLine(" return true;"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine(" table = null;"); + builder.AppendLine(" return false;"); + builder.AppendLine(" }"); + builder.AppendLine("}"); + return builder.ToString().TrimEnd(); + } + /// /// 递归生成配置对象类型。 /// @@ -773,6 +907,32 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator return Path.GetFileNameWithoutExtension(fileName); } + /// + /// 解析生成注册辅助时要使用的 schema 相对路径。 + /// 生成器优先保留 `schemas/` 目录以下的相对路径,以便消费端默认约定和 MSBuild AdditionalFiles 约定保持一致。 + /// + /// Schema 文件路径。 + /// 用于运行时注册的 schema 相对路径。 + private static string GetSchemaRelativePath(string path) + { + var normalizedPath = path.Replace('\\', '/'); + const string rootMarker = "schemas/"; + const string nestedMarker = "/schemas/"; + + if (normalizedPath.StartsWith(rootMarker, StringComparison.OrdinalIgnoreCase)) + { + return normalizedPath; + } + + var nestedMarkerIndex = normalizedPath.LastIndexOf(nestedMarker, StringComparison.OrdinalIgnoreCase); + if (nestedMarkerIndex >= 0) + { + return normalizedPath.Substring(nestedMarkerIndex + 1); + } + + return $"schemas/{Path.GetFileName(path)}"; + } + /// /// 将 schema 名称转换为 PascalCase 标识符。 /// @@ -996,19 +1156,29 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator /// 生成器级 schema 模型。 /// /// Schema 文件名。 + /// 实体名基础标识。 /// 根配置类型名。 /// 配置表包装类型名。 /// 目标命名空间。 /// 主键 CLR 类型。 + /// 生成配置类型中的主键属性名。 + /// 运行时注册名。 + /// 配置目录相对路径。 + /// Schema 文件相对路径。 /// 根标题元数据。 /// 根描述元数据。 /// 根对象模型。 private sealed record SchemaFileSpec( string FileName, + string EntityName, string ClassName, string TableName, string Namespace, string KeyClrType, + string KeyPropertyName, + string TableRegistrationName, + string ConfigRelativePath, + string SchemaRelativePath, string? Title, string? Description, SchemaObjectSpec RootObject); diff --git a/docs/zh-CN/game/config-system.md b/docs/zh-CN/game/config-system.md index 64eb7726..c2f1138c 100644 --- a/docs/zh-CN/game/config-system.md +++ b/docs/zh-CN/game/config-system.md @@ -12,7 +12,7 @@ - JSON Schema 作为结构描述 - 一对象一文件的目录组织 - 运行时只读查询 -- Source Generator 生成配置类型和表包装 +- Source Generator 生成配置类型、表包装和注册/访问辅助 - VS Code 插件提供配置浏览、raw 编辑、schema 打开、递归轻量校验和嵌套对象表单入口 ## 推荐目录结构 @@ -83,27 +83,31 @@ dropItems: ## 运行时接入 -当你希望加载后的配置在运行时以只读表形式暴露时,可以使用 `YamlConfigLoader` 和 `ConfigRegistry`: +当你希望加载后的配置在运行时以只读表形式暴露时,优先使用生成器产出的注册与访问辅助: ```csharp using GFramework.Game.Config; +using GFramework.Game.Config.Generated; var registry = new ConfigRegistry(); var loader = new YamlConfigLoader("config-root") - .RegisterTable( - "monster", - "monster", - "schemas/monster.schema.json", - static config => config.Id); + .RegisterMonsterTable(); await loader.LoadAsync(registry); -var monsterTable = registry.GetTable("monster"); +var monsterTable = registry.GetMonsterTable(); var slime = monsterTable.Get(1); ``` -这个重载会先按 schema 校验,再进行反序列化和注册。 +这组辅助会把以下约定固化到生成代码里: + +- 表注册名,例如 `monster` +- 配置目录相对路径,例如 `monster` +- schema 相对路径,例如 `schemas/monster.schema.json` +- 主键提取逻辑,例如 `config => config.Id` + +如果你需要自定义目录、表名或 key selector,仍然可以直接调用 `YamlConfigLoader.RegisterTable(...)` 原始重载。 ## 运行时校验行为 @@ -187,7 +191,12 @@ var hotReload = loader.EnableHotReload( ## 生成器接入约定 -配置生成器会从 `*.schema.json` 生成配置类型和表包装类。 +配置生成器会从 `*.schema.json` 生成以下代码: + +- 配置类型 +- 表包装类型 +- `YamlConfigLoader` 注册辅助 +- `IConfigRegistry` 强类型访问辅助 通过已打包的 Source Generator 使用时,默认会自动收集 `schemas/**/*.schema.json` 作为 `AdditionalFiles`。 From 3bca6390ce1d0c652948783458ae9bff991a105a Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Fri, 3 Apr 2026 09:53:51 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat(config):=20=E6=B7=BB=E5=8A=A0JSON=20sc?= =?UTF-8?q?hema=E9=85=8D=E7=BD=AE=E7=94=9F=E6=88=90=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现了根据JSON schema自动生成配置类型和配置表包装的功能 - 支持嵌套对象、对象数组、标量数组的类型生成 - 提供可映射的default/enum/ref-table元数据支持 - 生成强类型的配置表包装器和运行时绑定辅助类 - 实现了完整的schema解析和C#代码生成功能 - 添加了详细的XML文档注释和错误诊断功能 --- .../SchemaConfigGenerator/MonsterConfigBindings.g.txt | 6 +++--- .../Config/SchemaConfigGenerator.cs | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/MonsterConfigBindings.g.txt b/GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/MonsterConfigBindings.g.txt index 9893f455..a2954ce7 100644 --- a/GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/MonsterConfigBindings.g.txt +++ b/GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/MonsterConfigBindings.g.txt @@ -5,7 +5,7 @@ namespace GFramework.Game.Config.Generated; /// /// Auto-generated registration and lookup helpers for schema file 'monster.schema.json'. -/// The helper centralizes table naming, config directory, schema path, and strong-typed registry access so consumer projects do not need to duplicate the same conventions. +/// The helper centralizes table naming, config directory, schema path, and strongly-typed registry access so consumer projects do not need to duplicate the same conventions. /// public static class MonsterConfigBindings { @@ -51,7 +51,7 @@ public static class MonsterConfigBindings /// Gets the generated config table wrapper from the registry. /// /// The source config registry. - /// The generated strong-typed table wrapper. + /// The generated strongly-typed table wrapper. /// When is null. public static MonsterTable GetMonsterTable(this global::GFramework.Game.Abstractions.Config.IConfigRegistry registry) { @@ -67,7 +67,7 @@ public static class MonsterConfigBindings /// Tries to get the generated config table wrapper from the registry. /// /// The source config registry. - /// The generated strong-typed table wrapper when lookup succeeds; otherwise null. + /// The generated strongly-typed table wrapper when lookup succeeds; otherwise null. /// True when the generated table is registered and type-compatible; otherwise false. /// When is null. public static bool TryGetMonsterTable(this global::GFramework.Game.Abstractions.Config.IConfigRegistry registry, out MonsterTable? table) diff --git a/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs b/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs index 6428f624..29d52fdb 100644 --- a/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs +++ b/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs @@ -1,7 +1,3 @@ -using System.Globalization; -using System.IO; -using System.Text; -using System.Text.Json; using GFramework.SourceGenerators.Diagnostics; namespace GFramework.SourceGenerators.Config; @@ -640,7 +636,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator builder.AppendLine( $"/// Auto-generated registration and lookup helpers for schema file '{schema.FileName}'."); builder.AppendLine( - "/// The helper centralizes table naming, config directory, schema path, and strong-typed registry access so consumer projects do not need to duplicate the same conventions."); + "/// The helper centralizes table naming, config directory, schema path, and strongly-typed registry access so consumer projects do not need to duplicate the same conventions."); builder.AppendLine("/// "); builder.AppendLine($"public static class {bindingsClassName}"); builder.AppendLine("{"); @@ -694,7 +690,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator builder.AppendLine(" /// Gets the generated config table wrapper from the registry."); builder.AppendLine(" /// "); builder.AppendLine(" /// The source config registry."); - builder.AppendLine(" /// The generated strong-typed table wrapper."); + builder.AppendLine(" /// The generated strongly-typed table wrapper."); builder.AppendLine( " /// When is null."); builder.AppendLine( @@ -714,7 +710,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator builder.AppendLine(" /// "); builder.AppendLine(" /// The source config registry."); builder.AppendLine( - " /// The generated strong-typed table wrapper when lookup succeeds; otherwise null."); + " /// The generated strongly-typed table wrapper when lookup succeeds; otherwise null."); builder.AppendLine( " /// True when the generated table is registered and type-compatible; otherwise false."); builder.AppendLine( From 76479eb9f8f22037cb829f1adb851e9b82bfdde7 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:04:18 +0800 Subject: [PATCH 3/3] =?UTF-8?q?refactor(GFramework.SourceGenerators):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=85=A8=E5=B1=80=E5=91=BD=E5=90=8D=E7=A9=BA?= =?UTF-8?q?=E9=97=B4=E5=BC=95=E7=94=A8=E4=BB=A5=E6=94=AF=E6=8C=81=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入 System.Globalization 用于区域设置相关操作 - 添加 System.IO 支持文件输入输出功能 - 集成 System.Text 提供文本处理能力 - 包含 System.Text.Json 用于 JSON 序列化反序列化 --- GFramework.SourceGenerators/GlobalUsings.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/GFramework.SourceGenerators/GlobalUsings.cs b/GFramework.SourceGenerators/GlobalUsings.cs index a23ec429..f098c130 100644 --- a/GFramework.SourceGenerators/GlobalUsings.cs +++ b/GFramework.SourceGenerators/GlobalUsings.cs @@ -20,4 +20,8 @@ global using System.Collections.Immutable; global using Microsoft.CodeAnalysis; global using Microsoft.CodeAnalysis.CSharp.Syntax; global using Microsoft.CodeAnalysis.CSharp; -global using Microsoft.CodeAnalysis.Text; \ No newline at end of file +global using Microsoft.CodeAnalysis.Text; +global using System.Globalization; +global using System.IO; +global using System.Text; +global using System.Text.Json; \ No newline at end of file