本文将告诉大家如何在分析器里面解析代码里面对于 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);
                });
        }
    }
}

本文的代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin ed913bc50dcdbdf67bd387cd49d0cfd2a95b4ede

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin ed913bc50dcdbdf67bd387cd49d0cfd2a95b4ede

获取代码之后,进入 LainewihereJerejawwerye 文件夹

更多源代码生成,请看官方的 Source Generators Cookbook

更多关于我博客请参阅 博客导航


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/IIncrementalGenerator-%E5%A2%9E%E9%87%8F-Source-Generator-%E7%94%9F%E6%88%90%E4%BB%A3%E7%A0%81%E5%85%A5%E9%97%A8-%E8%AF%BB%E5%8F%96%E8%A7%A3%E6%9E%90-ValueTuple-%E7%9A%84%E5%AE%9A%E4%B9%89.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航

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

微软最具价值专家


无盈利,不卖课,做纯粹的技术博客

以下是广告时间

推荐关注 Edi.Wang 的公众号

欢迎进入 Eleven 老师组建的 .NET 社区

以上广告全是友情推广,无盈利