C#.NET源生成器定名规则

[复制链接]
发表于 2026-9-30 00:17:40 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
一、 媒介



  • 本文讲解在C#.NET源生成器中生成类的字段、属性、方法及参数和变量标识符的定名处理方式
二、 C# 定名规则

1. CamelCase(小驼峰)‌



  • 首字母小写,后续单词首字母大写
  • 参数和变量适用
  • 私有字段使用下划线 + 小驼峰
2. PascalCase (帕斯卡、大驼峰)‌



  • 首字母大写,后续单词首字母大写
  • 适用属性、方法及公开字段
3. 开源项目Hand.Naming用于定名



  • dotnet add package Hand.Naming --version 0.3.2.1-alpha
3.1 CamelWordRule 组件



  • CamelWordRule.FistToLower处理小驼峰
  • 可以把大驼峰转化为小驼峰
  • 可以把下划线 + 小驼峰转化为小驼峰
  • 本身就是小驼峰的不受影响
  • 适用把属性名、字段名转化为参数名或变量名
  1. [Theory]
  2. [InlineData("Test", "test")]
  3. [InlineData("_id", "id")]
  4. [InlineData("name", "name")]
  5. public void FistToLower(string original, string expected)
  6. {
  7.     var result = CamelWordRule.FistToLower(original!);
  8.     Assert.Equal(expected, result);
  9. }
复制代码
3.2 UnderWordRule 组件



  • UnderWordRule.UnderLower处理下划线 + 小驼峰
  • 可以把大、小驼峰转化为下划线 + 小驼峰
  • 本身就是下划线 + 小驼峰的不受影响
  • 适用把参数名、属性名及公开字段名转化为私有字段名
  1. [Theory]
  2. [InlineData("Test", "_test")]
  3. [InlineData("test", "_test")]
  4. [InlineData("_test", "_test")]
  5. [InlineData("_Test", "_test")]
  6. public void UnderLower(string original, string expected)
  7. {
  8.     var result = UnderWordRule.UnderLower(original!);
  9.     Assert.Equal(expected, result);
  10. }
复制代码
3.3 PascalWordRule 组件



  • PascalWordRule.FistToUpper处理大驼峰
  • 可以下划线 + 小驼峰及小驼峰转化转化为大驼峰
  • 本身就是大驼峰的不受影响
  • 适用把参数名和私有字段名转化为属性和公开字段名
  1. [Theory]
  2. [InlineData("test", "Test")]
  3. [InlineData("_id", "Id")]
  4. [InlineData("Name", "Name")]
  5. public void FistToUpper(string original, string expected)
  6. {
  7.     var result = PascalWordRule.FistToUpper(original!);
  8.     Assert.Equal(expected, result);
  9. }
复制代码
三、 开源项目Hand.Generators.Naming处理成员名冲突



  • dotnet add package Hand.Generators.Naming --version 0.3.0-alpha
1. 按User生成UserDto的Case



  • 此中 UserDto 中已经存在Id属性了
  • 通过 Hand.Naming.KindContainer 组件处理定名冲突
  • 通过 Contains 方法判断是否含同名成员
  • 通过 AddKind 方法添加新属性
  1. var source = @"
  2.     public partial class UserDto
  3.     {
  4.         public string? Id { get; set; }
  5.     }
  6.     public record User(int Id, string Name);
  7.     ";
  8. var driver = SyntaxTreeDriver.CreateDefaultDriver();
  9. SyntaxTree syntaxTree = driver.Parse(source);
  10. var destType = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
  11. Assert.NotNull(destType);
  12. var compilation = driver.Compile(syntaxTree);
  13. var sourceSymbol = compilation.GetTypeByMetadataName("User");
  14. Assert.NotNull(sourceSymbol);
  15. var destSymbol = compilation.GetTypeByMetadataName("UserDto");
  16. Assert.NotNull(destSymbol);
  17. var nameProvider = KindContainer.Create(destSymbol);
  18. var sourceProperties = SymbolReflection.GetPublicPropertiesWithBase(sourceSymbol).ToArray();
  19. var generator = SyntaxGenerator.Clone(destType);
  20. foreach (var sourceProperty in sourceProperties)
  21. {
  22.     var propertyName = sourceProperty.Name;
  23.     if (nameProvider.Contains(propertyName))
  24.         continue;
  25.     var property = sourceProperty.Type.ToSyntax().GetSetProperty(propertyName).Public();
  26.     generator.AddProperty(property);
  27.     nameProvider.AddKind(propertyName, MemberKind.PropertyDeclaration);
  28. }
  29. var code = generator.Build().NormalizeWhitespace().ToFullString();
  30. Assert.Contains("Name", code);
  31. Assert.DoesNotContain("Id", code);
复制代码
2. UserDto生成代码如下

  1. partial class UserDto
  2. {
  3.     public string Name { get; set; }
  4. }
复制代码
3. KindContainer 重要方法

  1. class KindContainer
  2. {
  3.     bool Contains(string name);
  4.     bool TryGetMemberKind(string name, out MemberKind kind);
  5.     void AddKind(string name, MemberKind kind);
  6. }
复制代码
4. KindContainer 调试结果


四、 成员不能与类同名



  • 除构造函数外,成员不能与当前类同名
1. 使用 TypedProvider 组件处理类名冲突



  • TypedProvider 可以实现与 KindContainer 类似的功能
  • TypedProvider 会过滤当前类名
  • 扩展方法 TryDeclareField,封装了 Contains 和 AddKind
  • TryDeclareProperty 也是扩展方法,用于属性的尝试添加
  1. var source = @"
  2.     public partial class B;
  3.     public record A(string Name, string B);
  4.     ";
  5. var driver = SyntaxTreeDriver.CreateDefaultDriver();
  6. SyntaxTree syntaxTree = driver.Parse(source);
  7. var destType = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
  8. Assert.NotNull(destType);
  9. var compilation = driver.Compile(syntaxTree);
  10. var sourceSymbol = compilation.GetTypeByMetadataName("A");
  11. Assert.NotNull(sourceSymbol);
  12. var destSymbol = compilation.GetTypeByMetadataName("B");
  13. Assert.NotNull(destSymbol);
  14. var nameProvider = TypedProvider.Create(destSymbol);
  15. var sourceProperties = SymbolReflection.GetPublicPropertiesWithBase(sourceSymbol).ToArray();
  16. var generator = SyntaxGenerator.Clone(destType);
  17. foreach (var sourceProperty in sourceProperties)
  18. {
  19.     var propertyName = sourceProperty.Name;
  20.     if (nameProvider.TryDeclareField(propertyName))
  21.     {
  22.         var field = sourceProperty.Type.ToSyntax()
  23.             .Field(propertyName, SyntaxGenerator.DefaultLiteral.SuppressNull())
  24.             .Public();
  25.         generator.AddField(field);
  26.     }
  27. }
  28. var code = generator.Build().NormalizeWhitespace().ToFullString();
  29. Assert.Contains("Name", code);
复制代码
2. 示例1生成如下代码



  • 由于类A的属性B与类B重名被 TypedProvider 过滤掉了
  • 如果示例2.1换成 KindContainer 生成的代码可能就编译不过了(CS0542)
  1. partial class B
  2. {
  3.     public string Name = default!;
  4. }
复制代码
五、方法支持重载

1. MethodContainer 组件支持处理重载



  • 通过 TryDeclareMethod 尝试添加方法签名
  • 虽然已经存在Add方法, 但新添加的Add方法参数类型不同,可以添加成功
  • MethodContainer 通过匹配参数个数、类型及次序实现重载逻辑判断
  1. var source = @"
  2.     public partial class Calculator
  3.     {
  4.         public static int Add(int a, int b) => a + b;
  5.     }
  6.     ";
  7. var driver = SyntaxTreeDriver.CreateDefaultDriver();
  8. SyntaxTree syntaxTree = driver.Parse(source);
  9. var destType = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
  10. Assert.NotNull(destType);
  11. var compilation = driver.Compile(syntaxTree);
  12. var destSymbol = compilation.GetTypeByMetadataName("Calculator");
  13. Assert.NotNull(destSymbol);
  14. var nameContainer = MethodContainer.Create(destSymbol);
  15. var generator = SyntaxGenerator.Clone(destType);
  16. var methodName = "Add";
  17. Assert.True(nameContainer.Contains(methodName));
  18. var methodSymbol = compilation.GetDecimalSymbol();
  19. INamedTypeSymbol[] parameterSymbols = [methodSymbol, methodSymbol];
  20. if (nameContainer.TryDeclareMethod(methodName, parameterSymbols))
  21. {
  22.     var methodType = methodSymbol.ToSyntax();
  23.     var a = SyntaxFactory.IdentifierName("a");
  24.     var b = SyntaxFactory.IdentifierName("b");
  25.     var method = methodType.Method(methodName, [methodType.Parameter(a.Identifier), methodType.Parameter(b.Identifier)])
  26.         .ToBuilder()
  27.         .Return(a.Add(b))
  28.         .Public()
  29.         .Static();
  30.     generator.AddMethod(method);
  31. }
  32. var code = generator.Build().NormalizeWhitespace().ToFullString();
  33. Assert.Contains(methodName, code);
复制代码
2. 示例1生成如下代码

  1. partial class Calculator
  2. {
  3.     public static decimal Add(decimal a, decimal b)
  4.     {
  5.         return a + b;
  6.     }
  7. }
复制代码
六、 综合应用



  • 如何既支持字段、属性又支持方法重载
  • 这就是 CompositeProvider 组件应用的场景
1. 使用 CompositeProvider 的 Case

  1. var source = @"
  2.     public partial class B
  3.     {
  4.         public string? Id { get; set;}
  5.         public void Deconstruct(out string? id)
  6.         {
  7.             id = Id;
  8.         }
  9.     }
  10.     public record A(int? Id, string Name, string B);
  11.     ";
  12. var driver = SyntaxTreeDriver.CreateDefaultDriver();
  13. SyntaxTree syntaxTree = driver.Parse(source);
  14. var destType = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
  15. Assert.NotNull(destType);
  16. var compilation = driver.Compile(syntaxTree);
  17. var sourceSymbol = compilation.GetTypeByMetadataName("A");
  18. Assert.NotNull(sourceSymbol);
  19. var destSymbol = compilation.GetTypeByMetadataName("B");
  20. Assert.NotNull(destSymbol);
  21. var nameProvider = CompositeProvider.Create(destSymbol);
  22. var sourceProperties = SymbolReflection.GetPublicPropertiesWithBase(sourceSymbol).ToArray();
  23. var sourceCount = sourceProperties.Length;
  24. var generator = SyntaxGenerator.Clone(destType);
  25. var parameterSymbol0 = compilation.GetNullable(compilation.GetStringSymbol());
  26. var parameterSymbols = new List<ITypeSymbol>(sourceCount) { parameterSymbol0 };
  27. var method = SyntaxGenerator.VoidType.Method("Deconstruct")
  28.     .ToBuilder()
  29.     .AddParameter(parameterSymbol0.ToSyntax().Parameter("id").Out())
  30.     .AddExpression(SyntaxFactory.IdentifierName("id").Assign(SyntaxFactory.IdentifierName("Id")));
  31. foreach (var sourceProperty in sourceProperties)
  32. {
  33.     var propertyName = sourceProperty.Name;
  34.     if (nameProvider.TryDeclareProperty(propertyName))
  35.     {
  36.         var propertySymbol = sourceProperty.Type;
  37.         parameterSymbols.Add(propertySymbol);
  38.         var property = propertySymbol.ToSyntax()
  39.             .GetSetProperty(propertyName)
  40.             .Public();
  41.         generator.AddProperty(property);
  42.         var parameter = SyntaxFactory.IdentifierName(CamelWordRule.FistToLower(propertyName));
  43.         method.AddParameter(propertySymbol.ToSyntax().Parameter(parameter.Identifier).Out())
  44.             .AddExpression(parameter.Assign(SyntaxFactory.IdentifierName(propertyName)));
  45.     }
  46. }
  47. if (nameProvider.TryDeclareMethod("Deconstruct", [.. parameterSymbols]))
  48.     generator.AddMethod(method.End().Public());
  49. var code = generator.Build().NormalizeWhitespace().ToFullString();
  50. Assert.Contains("Name", code);
  51. Assert.Contains("Deconstruct", code);
复制代码
2. 示例1生成如下代码

  1. partial class B
  2. {
  3.     public string Name { get; set; }
  4.     public void Deconstruct(out string? id, out string name)
  5.     {
  6.         id = Id;
  7.         name = Name;
  8.     }
  9. }
复制代码
七、处理基类成员冲突



  • 可以添加 new 来解决与基类成员重名的题目
  • InheritProvider 组件可以处理这种题目
1. 使用 InheritProvider 的 Case



  • 通过方法 TypedProvider.Inherit 构造 InheritProvider
  • 通过 TryDeclareField 的 isNew 输出参数判断是否与基类重名
  • 如果重名增加 new 修饰符
  1. var source = @"
  2.     public record Column(string Name);
  3.     public abstract class Table(string name)
  4.     {
  5.         public string Name { get; } = name;
  6.         public abstract Column[] Columns { get; }
  7.     }
  8.     public record User(int Id, string Name);
  9.     public partial class UserTable() : Table(""Users"");
  10. ";
  11. var driver = SyntaxTreeDriver.CreateDefaultDriver();
  12. SyntaxTree syntaxTree = driver.Parse(source);
  13. var destType = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().LastOrDefault();
  14. Assert.NotNull(destType);
  15. var compilation = driver.Compile(syntaxTree);
  16. var sourceSymbol = compilation.GetTypeByMetadataName("User");
  17. Assert.NotNull(sourceSymbol);
  18. var destSymbol = compilation.GetTypeByMetadataName("UserTable");
  19. Assert.NotNull(destSymbol);
  20. var columnType = SyntaxFactory.IdentifierName("Column");
  21. InheritProvider nameProvider = TypedProvider.Inherit(destSymbol);
  22. var sourceProperties = SymbolReflection.GetPublicPropertiesWithBase(sourceSymbol).ToArray();
  23. var generator = SyntaxGenerator.Clone(destType);
  24. var columnList = new List<ExpressionSyntax>();
  25. foreach (var sourceProperty in sourceProperties)
  26. {
  27.     var columnName = sourceProperty.Name;
  28.     if (nameProvider.TryDeclareField(columnName, out var isNew))
  29.     {
  30.         var column = SyntaxFactory.IdentifierName(columnName);
  31.         var field = columnType
  32.             .Field(column.Identifier, SyntaxGenerator.New([SyntaxGenerator.Literal(columnName)]))
  33.             .Public();
  34.         if (isNew)
  35.             field = field.New();
  36.         generator.AddField(field);
  37.         columnList.Add(column);
  38.     }
  39. }
  40. var columns = columnType.Array()
  41.     .Property("Columns", SyntaxGenerator.Collection([.. columnList]))
  42.     .Public()
  43.     .Override();
  44. generator.AddProperty(columns);
  45. var code = generator.Build().NormalizeWhitespace().ToFullString();
  46. Assert.Contains("new Column Name", code);
复制代码
2. 示例1生成如下代码

  1. partial class UserTable
  2. {
  3.     public Column Id = new("Id");
  4.     public new Column Name = new("Name");
  5.     public override Column[] Columns => [Id, Name];
  6. }
复制代码
八、最佳实践总结

1. 使用 Hand.Naming 规范化标识符定名



  • CamelWordRule处理参数名或变量名
  • UnderWordRule处理私有字段名
  • PascalWordRule处理属性、方法、类及公开字段名
  • github仓库地址: https://github.com/donetsoftwork/HandCore.net/tree/master/Hand.Naming
  • gitee同步跟新仓库地址: https://gitee.com/donetsoftwork/HandCore.net/tree/master/Hand.Naming
2. 使用 Hand.Generators.Naming 处理定名冲突



  • 如果需要定义新函数推荐使用 CompositeProvider 组件
  • 如果只需要字段和属性推荐使用 TypedProvider 组件
  • 如果涉及对基类成员名的重写,使用 Inherit
  • github仓库地址: https://github.com/donetsoftwork/Hand.Generators/tree/master/Hand.Generators.Naming
  • gitee同步跟新仓库地址: https://gitee.com/donetsoftwork/hand.-generators/tree/master/Hand.Generators.Naming

免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作! qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金.
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表