var source = @"
public partial class UserDto
{
public string? Id { get; set; }
}
public record User(int Id, string Name);
";
var driver = SyntaxTreeDriver.CreateDefaultDriver();
SyntaxTree syntaxTree = driver.Parse(source);
var destType = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
Assert.NotNull(destType);
var compilation = driver.Compile(syntaxTree);
var sourceSymbol = compilation.GetTypeByMetadataName("User");
Assert.NotNull(sourceSymbol);
var destSymbol = compilation.GetTypeByMetadataName("UserDto");
Assert.NotNull(destSymbol);
var nameProvider = KindContainer.Create(destSymbol);
var sourceProperties = SymbolReflection.GetPublicPropertiesWithBase(sourceSymbol).ToArray();
var generator = SyntaxGenerator.Clone(destType);
foreach (var sourceProperty in sourceProperties)
{
var propertyName = sourceProperty.Name;
if (nameProvider.Contains(propertyName))
continue;
var property = sourceProperty.Type.ToSyntax().GetSetProperty(propertyName).Public();
generator.AddProperty(property);
nameProvider.AddKind(propertyName, MemberKind.PropertyDeclaration);
}
var code = generator.Build().NormalizeWhitespace().ToFullString();
Assert.Contains("Name", code);
Assert.DoesNotContain("Id", code);
2. UserDto生成代码如下
csharp复制代码
partial class UserDto
{
public string Name { get; set; }
}
3. KindContainer 主要方法
csharp复制代码
class KindContainer
{
bool Contains(string name);
bool TryGetMemberKind(string name, out MemberKind kind);
void AddKind(string name, MemberKind kind);
}
4. KindContainer 调试结果
四、 成员不能与类同名
除构造函数外,成员不能与当前类同名
1. 使用 TypedProvider 组件处理类名冲突
TypedProvider 可以实现与 KindContainer 类似的功能
TypedProvider 会过滤当前类名
扩展方法 TryDeclareField,封装了 Contains 和 AddKind
TryDeclareProperty 也是扩展方法,用于属性的尝试添加
csharp复制代码
var source = @"
public partial class B;
public record A(string Name, string B);
";
var driver = SyntaxTreeDriver.CreateDefaultDriver();
SyntaxTree syntaxTree = driver.Parse(source);
var destType = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
Assert.NotNull(destType);
var compilation = driver.Compile(syntaxTree);
var sourceSymbol = compilation.GetTypeByMetadataName("A");
Assert.NotNull(sourceSymbol);
var destSymbol = compilation.GetTypeByMetadataName("B");
Assert.NotNull(destSymbol);
var nameProvider = TypedProvider.Create(destSymbol);
var sourceProperties = SymbolReflection.GetPublicPropertiesWithBase(sourceSymbol).ToArray();
var generator = SyntaxGenerator.Clone(destType);
foreach (var sourceProperty in sourceProperties)
{
var propertyName = sourceProperty.Name;
if (nameProvider.TryDeclareField(propertyName))
{
var field = sourceProperty.Type.ToSyntax()
.Field(propertyName, SyntaxGenerator.DefaultLiteral.SuppressNull())
.Public();
generator.AddField(field);
}
}
var code = generator.Build().NormalizeWhitespace().ToFullString();
Assert.Contains("Name", code);
2. 示例1生成如下代码
由于类A的属性B与类B重名被 TypedProvider 过滤掉了
如果示例2.1换成 KindContainer 生成的代码可能就编译不过了(CS0542)
csharp复制代码
partial class B
{
public string Name = default!;
}
五、方法支持重载
1. MethodContainer 组件支持处理重载
通过 TryDeclareMethod 尝试添加方法签名
虽然已经存在Add方法, 但新添加的Add方法参数类型不同,可以添加成功
MethodContainer 通过匹配参数个数、类型及顺序实现重载逻辑判断
csharp复制代码
var source = @"
public partial class Calculator
{
public static int Add(int a, int b) => a + b;
}
";
var driver = SyntaxTreeDriver.CreateDefaultDriver();
SyntaxTree syntaxTree = driver.Parse(source);
var destType = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
Assert.NotNull(destType);
var compilation = driver.Compile(syntaxTree);
var destSymbol = compilation.GetTypeByMetadataName("Calculator");
Assert.NotNull(destSymbol);
var nameContainer = MethodContainer.Create(destSymbol);
var generator = SyntaxGenerator.Clone(destType);
var methodName = "Add";
Assert.True(nameContainer.Contains(methodName));
var methodSymbol = compilation.GetDecimalSymbol();
INamedTypeSymbol[] parameterSymbols = [methodSymbol, methodSymbol];
if (nameContainer.TryDeclareMethod(methodName, parameterSymbols))
{
var methodType = methodSymbol.ToSyntax();
var a = SyntaxFactory.IdentifierName("a");
var b = SyntaxFactory.IdentifierName("b");
var method = methodType.Method(methodName, [methodType.Parameter(a.Identifier), methodType.Parameter(b.Identifier)])
.ToBuilder()
.Return(a.Add(b))
.Public()
.Static();
generator.AddMethod(method);
}
var code = generator.Build().NormalizeWhitespace().ToFullString();
Assert.Contains(methodName, code);
2. 示例1生成如下代码
csharp复制代码
partial class Calculator
{
public static decimal Add(decimal a, decimal b)
{
return a + b;
}
}
六、 综合应用
如何既支持字段、属性又支持方法重载
这就是 CompositeProvider 组件应用的场景
1. 使用 CompositeProvider 的 Case
csharp复制代码
var source = @"
public partial class B
{
public string? Id { get; set;}
public void Deconstruct(out string? id)
{
id = Id;
}
}
public record A(int? Id, string Name, string B);
";
var driver = SyntaxTreeDriver.CreateDefaultDriver();
SyntaxTree syntaxTree = driver.Parse(source);
var destType = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
Assert.NotNull(destType);
var compilation = driver.Compile(syntaxTree);
var sourceSymbol = compilation.GetTypeByMetadataName("A");
Assert.NotNull(sourceSymbol);
var destSymbol = compilation.GetTypeByMetadataName("B");
Assert.NotNull(destSymbol);
var nameProvider = CompositeProvider.Create(destSymbol);
var sourceProperties = SymbolReflection.GetPublicPropertiesWithBase(sourceSymbol).ToArray();
var sourceCount = sourceProperties.Length;
var generator = SyntaxGenerator.Clone(destType);
var parameterSymbol0 = compilation.GetNullable(compilation.GetStringSymbol());
var parameterSymbols = new List<ITypeSymbol>(sourceCount) { parameterSymbol0 };
var method = SyntaxGenerator.VoidType.Method("Deconstruct")
.ToBuilder()
.AddParameter(parameterSymbol0.ToSyntax().Parameter("id").Out())
.AddExpression(SyntaxFactory.IdentifierName("id").Assign(SyntaxFactory.IdentifierName("Id")));
foreach (var sourceProperty in sourceProperties)
{
var propertyName = sourceProperty.Name;
if (nameProvider.TryDeclareProperty(propertyName))
{
var propertySymbol = sourceProperty.Type;
parameterSymbols.Add(propertySymbol);
var property = propertySymbol.ToSyntax()
.GetSetProperty(propertyName)
.Public();
generator.AddProperty(property);
var parameter = SyntaxFactory.IdentifierName(CamelWordRule.FistToLower(propertyName));
method.AddParameter(propertySymbol.ToSyntax().Parameter(parameter.Identifier).Out())
.AddExpression(parameter.Assign(SyntaxFactory.IdentifierName(propertyName)));
}
}
if (nameProvider.TryDeclareMethod("Deconstruct", [.. parameterSymbols]))
generator.AddMethod(method.End().Public());
var code = generator.Build().NormalizeWhitespace().ToFullString();
Assert.Contains("Name", code);
Assert.Contains("Deconstruct", code);
2. 示例1生成如下代码
csharp复制代码
partial class B
{
public string Name { get; set; }
public void Deconstruct(out string? id, out string name)
{
id = Id;
name = Name;
}
}
七、处理基类成员冲突
可以添加 new 来解决与基类成员重名的问题
InheritProvider 组件可以处理这种问题
1. 使用 InheritProvider 的 Case
通过方法 TypedProvider.Inherit 构造 InheritProvider
通过 TryDeclareField 的 isNew 输出参数判断是否与基类重名
如果重名增加 new 修饰符
csharp复制代码
var source = @"
public record Column(string Name);
public abstract class Table(string name)
{
public string Name { get; } = name;
public abstract Column[] Columns { get; }
}
public record User(int Id, string Name);
public partial class UserTable() : Table(""Users"");
";
var driver = SyntaxTreeDriver.CreateDefaultDriver();
SyntaxTree syntaxTree = driver.Parse(source);
var destType = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().LastOrDefault();
Assert.NotNull(destType);
var compilation = driver.Compile(syntaxTree);
var sourceSymbol = compilation.GetTypeByMetadataName("User");
Assert.NotNull(sourceSymbol);
var destSymbol = compilation.GetTypeByMetadataName("UserTable");
Assert.NotNull(destSymbol);
var columnType = SyntaxFactory.IdentifierName("Column");
InheritProvider nameProvider = TypedProvider.Inherit(destSymbol);
var sourceProperties = SymbolReflection.GetPublicPropertiesWithBase(sourceSymbol).ToArray();
var generator = SyntaxGenerator.Clone(destType);
var columnList = new List<ExpressionSyntax>();
foreach (var sourceProperty in sourceProperties)
{
var columnName = sourceProperty.Name;
if (nameProvider.TryDeclareField(columnName, out var isNew))
{
var column = SyntaxFactory.IdentifierName(columnName);
var field = columnType
.Field(column.Identifier, SyntaxGenerator.New([SyntaxGenerator.Literal(columnName)]))
.Public();
if (isNew)
field = field.New();
generator.AddField(field);
columnList.Add(column);
}
}
var columns = columnType.Array()
.Property("Columns", SyntaxGenerator.Collection([.. columnList]))
.Public()
.Override();
generator.AddProperty(columns);
var code = generator.Build().NormalizeWhitespace().ToFullString();
Assert.Contains("new Column Name", code);
2. 示例1生成如下代码
csharp复制代码
partial class UserTable
{
public Column Id = new("Id");
public new Column Name = new("Name");
public override Column[] Columns => [Id, Name];
}