本文将告诉大家如何在分析器里面解析代码里面对于 ValueTuple 的定义,包括如何获取 ValueTuple 里面的 Item 的类型和命名
开始之前先创建一个用来被分析的项目,在这个项目里面定义 Foo1 类型,然后再定义 F2 方法,设置 F2 方法的返回值是一个 ValueTuple 类型,如以下代码
public class Foo1{ public (int x, int) F2() { return default; }}
本文将使用此作为例子,告诉大家如何解析 ValueTuple 的定义,也就是获取 F2 方法返回值类型的定义
先编写语法过滤,只让方法定义的语法通过,如以下代码
[Generator(LanguageNames.CSharp)] public class CodeCollectionIncrementalGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { var incrementalValuesProvider = context.SyntaxProvider.CreateSyntaxProvider((node, _) => { // 只读取方法定义。这里例子是读取一个方法的返回值 return node.IsKind(SyntaxKind.MethodDeclaration); }, (syntaxContext, _) => { ... // 忽略代码 });
... // 忽略代码 }
因为本文是使用分析 F2 方法的返回值作为例子,因此上面代码只让方法定义的语法通过
接下来获取方法的返回值,如以下代码
var incrementalValuesProvider = context.SyntaxProvider.CreateSyntaxProvider((node, _) => { // 只读取方法定义。这里例子是读取一个方法的返回值 return node.IsKind(SyntaxKind.MethodDeclaration); }, (syntaxContext, _) => { // 转换为语义 IMethodSymbol methodSymbol = syntaxContext.SemanticModel.GetDeclaredSymbol(syntaxContext.Node) as IMethodSymbol; if (methodSymbol is null) { // 理论上不会进入这个代码,前面判断方法定义 return string.Empty; }
// 获取方法返回类型 ITypeSymbol methodSymbolReturnType = methodSymbol.ReturnType; if (methodSymbolReturnType is INamedTypeSymbol namedTypeSymbol) { }
... // 忽略代码
}).Where(t => !string.IsNullOrEmpty(t));
拿到方法返回值,可以进行判断 ValueTuple 类型,判断方法是通过先判断是否值类型,再判断 Tuple 元素的数量
// 由于 ValueTuple 是值类型,因此可以快速判断是否值类型 if (namedTypeSymbol.IsValueType && namedTypeSymbol.TupleElements.Length > 0) { }
获取 ValueTuple 语义上定义的各个 Item 的类型和命名可以通过遍历 TupleElements 属性
foreach (var tupleElement in namedTypeSymbol.TupleElements) { // 如此可以获取每一个类型 var tupleElementType = tupleElement.Type; }
如果用户代码里面没有给 Item 命名,将会自动填充默认的 Item1 等默认命名,如果想要获取原来代码的定义,可以获取语法内容,如以下代码
var code = namedTypeSymbol.DeclaringSyntaxReferences[0].GetSyntax().ToString();
以下是 CodeCollectionIncrementalGenerator 的全部代码,可以创建出一个 Foo 类型,用来输出 Foo1 的 F2 方法的返回代码
[Generator(LanguageNames.CSharp)] public class CodeCollectionIncrementalGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { var incrementalValuesProvider = context.SyntaxProvider.CreateSyntaxProvider((node, _) => { // 只读取方法定义。这里例子是读取一个方法的返回值 return node.IsKind(SyntaxKind.MethodDeclaration); }, (syntaxContext, _) => { // 转换为语义 IMethodSymbol methodSymbol = syntaxContext.SemanticModel.GetDeclaredSymbol(syntaxContext.Node) as IMethodSymbol; if (methodSymbol is null) { // 理论上不会进入这个代码,前面判断方法定义 return string.Empty; }
// 获取方法返回类型 ITypeSymbol methodSymbolReturnType = methodSymbol.ReturnType; if (methodSymbolReturnType is INamedTypeSymbol namedTypeSymbol) { // 由于 ValueTuple 是值类型,因此可以快速判断是否值类型 if (namedTypeSymbol.IsValueType && namedTypeSymbol.TupleElements.Length > 0) { foreach (var tupleElement in namedTypeSymbol.TupleElements) { // 如此可以获取每一个类型 var tupleElementType = tupleElement.Type; }
var code = namedTypeSymbol.DeclaringSyntaxReferences[0].GetSyntax().ToString(); return code; } }
return ""; }).Where(t => !string.IsNullOrEmpty(t));
context.RegisterImplementationSourceOutput(incrementalValuesProvider, (productionContext, provider) => { var text = provider;
var code = @"using System;namespace LainewihereJerejawwerye{ public static class Foo { public static void F1() { Console.WriteLine(""Foo1.F2 方法返回值是" + text + @"""); } }}"; productionContext.AddSource("Demo", code); }); } }}
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git initgit remote add origin https://gitee.com/lindexi/lindexi_gd.gitgit pull origin ed913bc50dcdbdf67bd387cd49d0cfd2a95b4ede
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码
git remote remove origingit remote add origin https://github.com/lindexi/lindexi_gd.gitgit pull origin ed913bc50dcdbdf67bd387cd49d0cfd2a95b4ede
获取代码之后,进入 LainewihereJerejawwerye 文件夹
更多源代码生成,请看官方的 Source Generators Cookbook
更多关于我博客请参阅 博客导航

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。 欢迎转载、使用、重新发布,但务必保留文章署名 林德熙 (包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我 联系。