百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

如何使用c#编写一个源生成器 3/5 :代码

bigegpt 2024-08-03 11:33 5 浏览

我们将实现IIncrementalGenerator,以便它生成我们所需的代码。

语法树

IIncrementalGenerator只有一个方法需要实现,BindablePropSG.cs应该是这样的:

namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {

            var fieldGroups = context.SyntaxProvider
                .CreateSyntaxProvider(
                    // 一个告诉我们感兴趣的代码片段的函数
                    // 对于语法树中的每一个节点,都会应用这个函数来判断语法节点是否会在Transform阶段被处理
                    predicate: IsBindableProp,
                    // Transform 是一个从 predicate 阶段接收输出的函数
                    // 它的输入是上一阶段接受的节点
                    // 在这个阶段,我们只需要提取所有必要的信息来生成代码(字段名、数据类型等)
                    transform: Transform
                )
                .Where(item => item is not (null, null))
                .Collect();
            
            // fieldGroups 是所有具有 BindableProp 属性的字段
            // Execute 是一个函数。 它将从 fieldGroups 生成代码
            context.RegisterSourceOutput(fieldGroups, Execute);
        }
    }
}

在继续之前,让我简要解释一下生成器发生了什么。

编译器会将源代码文件解析为语法树,然后将其传递给我们的生成器。 然后生成器分析并创建新代码。 然后,将它们返回给编译器。

C# 文件语法树

我使用的是 Visual Studio 2022。如果找不到 Syntax Visualizer 窗口,请查看 Visual Installer 以安装 .NET 平台编译器 SDK。

我希望以上面的说明能让您了解发生了什么。 让我详细展示一下是如何实现所有功能的。

// BindableProp.cs:
namespace BindableProps
{
    [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
    public sealed class BindableProp : Attribute
    {
        public int DefaultBindingMode { get; set; }

        public string ValidateValueDelegate { get; set; }

        public string PropertyChangedDelegate { get; set; }

        public string PropertyChangingDelegate { get; set; }

        public string CoerceValueDelegate { get; set; }

        public string CreateDefaultValueDelegate { get; set; }


        public BindableProp()
        {

        }
    }
}
// BindablePropsSG/Generators/BindablePropSG.cs

namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
          // ...           
        }

        // 寻找标有 BindableProp 属性的代码
        private bool IsBindableProp(SyntaxNode node, CancellationToken _)
        {
            if (node is not AttributeSyntax attributeSyntax)
            {
                return false;
            }

            var name = SyntaxUtil.ExtractName(attributeSyntax?.Name);

            return name is "BindableProp" or "BindablePropAttribute";
        }
    }
}
// Utils/SyntaxUtil.cs

namespace BindablePropsSG.Utils
{
    public class SyntaxUtil
    {
        public static string? ExtractName(NameSyntax? name)
        {
            return name switch
            {
                SimpleNameSyntax ins => ins.Identifier.Text,
                QualifiedNameSyntax qns => qns.Right.Identifier.Text,
                _ => null
            };
        }
    }
}

现在我们拿到了所有 BindableProp 语法节点。 让我们提取必要的信息。

namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
          // ...           
        }

        private bool IsBindableProp(SyntaxNode node, CancellationToken _)
        {
            // ...
        }

        // 注意,我们返回一个元组
        // 对于那些不知道元组是什么的人,只要认为我返回一个包含 2 个项目的数组
        private (FieldDeclarationSyntax?, IFieldSymbol?) Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken)
        {
            // context对象包含很多东西,您可以调试和探索适合您需要的内容
            // 就我而言,FieldDeclarationSyntax 和 IFieldSymbol 足以

            // 这是从 IsBindableProp 筛选出来的语法节点
            var attributeSyntax = (AttributeSyntax)context.Node;

            // Attribute --> AttributeList --> Field
            // 通过上面的语法树图像,您将看到对应信息
            if (attributeSyntax.Parent?.Parent is not FieldDeclarationSyntax fieldSyntax)
                return (null, null);

            var fieldSymbol = context.SemanticModel.GetDeclaredSymbol(fieldSyntax.Declaration.Variables.FirstOrDefault()!) as IFieldSymbol;

            return (fieldSyntax, fieldSymbol);
        }
    }
}

要为 BindableProperty.Create 函数生成代码。 这个需要:

  • 字段名称
  • 他的数据类型
  • 类的名称
  • 默认值
  • 可选的绑定模式和一些委托函数

对于必选的参数,FieldDeclarationSyntax 和 IFieldSymbol 就足够了。 对于可选参数,让开发人员将它们输入到 BindableProp 的属性中。

// 这是开发人员使用所有设置时的示例

public partial class TextInput : ContentView
{
    // 使用一些设置创建属性
    [BindableProp(DefaultBindingMode = ((int)BindingMode.TwoWay))]
    string text = "From every time";

    // 完整设置
    [BindableProp(
        DefaultBindingMode = ((int)BindingMode.OneWay),
        ValidateValueDelegate = nameof(ValidateValue),
        PropertyChangedDelegate = nameof(PropertyChangedDelegate),
        PropertyChangingDelegate = nameof(PropertyChangingDelegate),
        CoerceValueDelegate = nameof(CoerceValueDelegate),
        CreateDefaultValueDelegate = nameof(CreateDefaultValueDelegate)
        )]
    string placeHolder = "Always!";

    static bool ValidateValue(BindableObject bindable, object value)
    {
        return true;
    }

    static void PropertyChangedDelegate(BindableObject bindable, object oldValue, object newValue)
    {
        // Do something
    }

    static void PropertyChangingDelegate(BindableObject bindable, object oldValue, object newValue)
    {
        // Do something
    }

    static object CoerceValueDelegate(BindableObject bindable, object value)
    {
        // Do something
        return 0;
    }

    static object CreateDefaultValueDelegate(BindableObject bindable)
    {
        // Do something
        return string.Empty;
    }
}

我们收集了足够的原料。 现在,让我们把它们放在一起。

namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
          // ...           
        }

        private bool IsBindableProp(SyntaxNode node, CancellationToken _)
        {
            // ...
        }

        private (FieldDeclarationSyntax?, IFieldSymbol?) Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken)
        {
            // ...
        }

        private void Execute(SourceProductionContext context, ImmutableArray<(FieldDeclarationSyntax?, IFieldSymbol?)> fieldSyntaxesAndSymbols)
        {
            // 转换后,得到一个元组

            if (fieldSyntaxesAndSymbols.IsDefaultOrEmpty)
                return;

            // 这些字段可能来自不同或相同的类
            // 我们需要按类别对它们进行分组
            var groupList = fieldSyntaxesAndSymbols.GroupBy<(FieldDeclarationSyntax, IFieldSymbol), ClassDeclarationSyntax>(
                    fieldGroup => (ClassDeclarationSyntax)fieldGroup.Item1!.Parent!
                );
            
            // 如果上面的代码让你头疼,我很抱歉
            // 几个月前,我写这个时也有同样的感觉

            // 你只需要知道
            // groupList 是map列表
            // 它的形式类似于下面的代码:
            // [ 
            //   { classDeclaration1, [tuple1, tuple2, ...] },
            //   { classDeclaration2, [tuple1, tuple2, ...] },
            //   ...
            // ]
            foreach (var group in groupList)
            {
                string sourceCode = ProcessClass(group.Key, group.ToList());
                // 全名包括命名空间和类名
                // 例如 MyMauiProject.Controls.Textlnput
                var className = SyntaxUtil.GetClassFullname(group.Key);

                // 注意,输出文件应该包含'g'字母
                // 这样您的 IDE 就可以知道这是生成的代码
                context.AddSource(#34;{className}.g.cs", sourceCode);
            }
        }
    }
}
//  BindablePropsSG 项目, Utils/SyntaxUtil.cs

namespace BindablePropsSG.Utils
{
    public class SyntaxUtil
    {
        public static string? ExtractName(NameSyntax? name)
        {
            // ...
        }

        public static string GetClassFullname(TypeDeclarationSyntax source)
        {
            var namespaces = new LinkedList<BaseNamespaceDeclarationSyntax>();
            var types = new LinkedList<TypeDeclarationSyntax>();

            for (var parent = source.Parent; parent is object; parent = parent.Parent)
            {
                if (parent is BaseNamespaceDeclarationSyntax @namespace)
                {
                    namespaces.AddFirst(@namespace);
                }
                else if (parent is TypeDeclarationSyntax type)
                {
                    types.AddFirst(type);
                }
            }

            var result = new StringBuilder();

            for (var item = namespaces.First; item is object; item = item.Next)
            {
                result.Append(item.Value.Name).Append(".");
            }

            for (var item = types.First; item is object; item = item.Next)
            {
                var type = item.Value;
                AppendName(result, type);
                result.Append(".");
            }

            AppendName(result, source);

            return result.ToString();
        }

        static void AppendName(StringBuilder builder, TypeDeclarationSyntax type)
        {
            builder.Append(type.Identifier.Text);
            var typeArguments = type.TypeParameterList?.ChildNodes()
                .Count(node => node is TypeParameterSyntax) ?? 0;
            if (typeArguments != 0)
                builder.Append(".").Append(typeArguments);
        }

        public static SyntaxNode? FindSyntaxBySymbol(SyntaxNode syntaxNode, ISymbol symbol)
        {
            var span = symbol.Locations.FirstOrDefault()!.SourceSpan;
            var syntax = syntaxNode.FindNode(span);

            return syntax;
        }
    }
}
namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
          // ...           
        }

        private bool IsBindableProp(SyntaxNode node, CancellationToken _)
        {
            // ...
        }

        private (FieldDeclarationSyntax?, IFieldSymbol?) Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken)
        {
            // ...
        }

        private void Execute(SourceProductionContext context, ImmutableArray<(FieldDeclarationSyntax?, IFieldSymbol?)> fieldSyntaxesAndSymbols)
        {
            // ...
        }

        private string ProcessClass(ClassDeclarationSyntax classSyntax, List<(FieldDeclarationSyntax, IFieldSymbol)> fieldGroup)
        {
            if (classSyntax is null)
            {
                return string.Empty;
            }

            // 导入所有的包
            var usingDirectives = classSyntax.SyntaxTree.GetCompilationUnitRoot().Usings;

            // 如果类没有命名空间,我们将使用'global'
            var namespaceSyntax = classSyntax.Parent as BaseNamespaceDeclarationSyntax;
            var namespaceName = namespaceSyntax?.Name?.ToString() ?? "global";

            // 放在一起
            // 制作类的部分
            var source = new StringBuilder($@"
// <auto-generated/>
{usingDirectives}

namespace {namespaceName}
{{
    public partial class {classSyntax.Identifier}
    {{
");

            // 来到字段部分
            // 为每个字段创建属性 
            foreach (var (fieldSynTax, fieldSymbol) in fieldGroup)
            {
                ProcessField(source, classSyntax, fieldSynTax, fieldSymbol);
            }

            // 不要忘记右括号
            source.Append(@#34;
    }}
}}
");
            return source.ToString();
        }
    }
}
namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
          // ...           
        }

        private bool IsBindableProp(SyntaxNode node, CancellationToken _)
        {
            // ...
        }

        private (FieldDeclarationSyntax?, IFieldSymbol?) Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken)
        {
            // ...
        }

        private void Execute(SourceProductionContext context, ImmutableArray<(FieldDeclarationSyntax?, IFieldSymbol?)> fieldSyntaxesAndSymbols)
        {
            // ...
        }

        private string ProcessClass(ClassDeclarationSyntax classSyntax, List<(FieldDeclarationSyntax, IFieldSymbol)> fieldGroup)
        {
            // ...
        }

        private void ProcessField(StringBuilder source, ClassDeclarationSyntax classSyntax, FieldDeclarationSyntax fieldSyntax, IFieldSymbol fieldSymbol)
        {
            var fieldName = fieldSymbol.Name;
            // C#中的命名约定,字段名是驼峰式,属性名是pascal形式
            // StringUtil.PascalCaseOf 位于 Utils/StringUtil.cs 中。 我稍后会展示它
            var propName = StringUtil.PascalCaseOf(fieldName);

            if (propName.Length == 0 || propName == fieldName)
            {
                return;
            }

            var fieldType = fieldSyntax.Declaration.Type;

            var className = classSyntax.Identifier;

            var defaultFieldValue = GetFieldDefaultValue(fieldSyntax) ?? "default";

            var attributeSyntax = GetAttributeByName(fieldSyntax, "BindableProp");

            var attributeArguments = attributeSyntax?.ArgumentList?.Arguments;

            // 得到可选参数
            var defaultBindingMode = GetAttributeParam(attributeArguments, "DefaultBindingMode") ?? "0";

            var validateValueDelegate = GetAttributeParam(attributeArguments, "ValidateValueDelegate") ?? "null";

            // 如果没有PropertyChangedDelegate
            // 我们给他一个默认的
            // 这将有助于属性与 MVVM 一起工作
            var propertyChangedDelegate = GetAttributeParam(
                attributeArguments, "PropertyChangedDelegate"
                ) ?? @#34;(bindable, oldValue, newValue) => 
                        (({className})bindable).{propName} = ({fieldType})newValue";

            var propertyChangingDelegate = GetAttributeParam(attributeArguments, "PropertyChangingDelegate") ?? "null";

            var coerceValueDelegate = GetAttributeParam(attributeArguments, "CoerceValueDelegate") ?? "null";

            var createDefaultValueDelegate = GetAttributeParam(attributeArguments, "CreateDefaultValueDelegate") ?? "null";

            // 检索所有必需和可选参数后,我们只需编写新的源代码
            source.Append($@"
        public static readonly BindableProperty {propName}Property = BindableProperty.Create(
            nameof({propName}),
            typeof({fieldType}),
            typeof({className}),
            {defaultFieldValue},
            (BindingMode){defaultBindingMode},
            {validateValueDelegate},
            {propertyChangedDelegate},
            {propertyChangingDelegate},
            {coerceValueDelegate},
            {createDefaultValueDelegate}
        );

        public {fieldType} {propName}
        {{
            get => {fieldName};
            set 
            {{ 
                OnPropertyChanging(nameof({propName}));

                {fieldName} = value;
                SetValue({className}.{propName}Property, {fieldName});

                OnPropertyChanged(nameof({propName}));
            }}
        }}
");
        }

        string? GetAttributeParam(SeparatedSyntaxList<AttributeArgumentSyntax>? attributeArguments, string paramName)
        {
            var paramSyntax = attributeArguments?.FirstOrDefault(
                attrArg => attrArg?.NameEquals?.Name.Identifier.Text == paramName
            );

            if (paramSyntax?.Expression is InvocationExpressionSyntax invocationExpressionSyntax)
            {
                return invocationExpressionSyntax.ArgumentList.Arguments.FirstOrDefault()?.ToString();
            }
            else if (paramSyntax?.Expression is LiteralExpressionSyntax literalExpressionSyntax)
            {
                return literalExpressionSyntax.Token.Value?.ToString();
            }

            return paramSyntax?.Expression.ToString();
        }

        string? GetFieldDefaultValue(FieldDeclarationSyntax fieldSyntax)
        {
            var variableDeclaration = fieldSyntax.DescendantNodesAndSelf()
                .OfType<VariableDeclarationSyntax>()
                .FirstOrDefault();
            var variableDeclarator = variableDeclaration?.Variables.FirstOrDefault();
            var initializer = variableDeclarator?.Initializer;
            return initializer?.Value?.ToString();
        }

        AttributeSyntax? GetAttributeByName(FieldDeclarationSyntax fieldSyntax, string attributeName)
        {
            var attributeSyntax = fieldSyntax.AttributeLists
                .FirstOrDefault(attrList =>
                {
                    var attr = attrList.Attributes.FirstOrDefault();
                    return attr is not null && SyntaxUtil.ExtractName(attr.Name) == attributeName;
                })
                ?.Attributes
                .FirstOrDefault();

            return attributeSyntax;
        }
    }
}
// Utils/StringUtil.cs

namespace BindablePropsSG.Utils
{
    public class StringUtil
    {
        public static string PascalCaseOf(string fieldName)
        {
            fieldName = fieldName.TrimStart('_');
            if (fieldName.Length == 0)
                return string.Empty;

            if (fieldName.Length == 1)
                return fieldName.ToUpper();

            return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1);
        }
    }
}

回顾

多么多的代码! 我试图以尽可能清晰的方式编写它们。 但是,可能仍然有许多您从未听说过的类和方法。 所以我来总结一下。

1.遍历抛出Syntax Tree,找到BindableProp属性节点

2.从我们找到的节点中,获取FieldDeclarationSyntax和IFieldSymbol

3.根据所属类别对它们进行分组

4.生成我们需要的代码并保存到文件中

在 MAUI 项目中使用时的样子

您可以尝试从单元测试项目运行或调试代码。

相关推荐

【机器学习】数据挖掘神器LightGBM详解(附代码)

来源:机器学习初学者本文约11000字,建议阅读20分钟本文为你介绍数据挖掘神器LightGBM。LightGBM是微软开发的boosting集成模型,和XGBoost一样是对GBDT...

3分钟,用DeepSeek全自动生成语音计算器,还带括号表达式!

最近,大家慢慢了解到了DeepSeek的强大功能,特别是它在编程领域也同样强大。编程零基础小白,一行代码不用写,也能全自动生成一个完整的、可运行的软件来!很多程序员一直不相信小白不写代码也能编软件!下...

python学习笔记 3.表达式

在Python中,表达式是由值、变量和运算符组成的组合。以下是一些常见的Python表达式:算术表达式:由数值和算术运算符组成的表达式,如加减乘除等。例如:5+3、7*2、10/3等。字符...

5.7 VS 8.x,为什么用户不升级MySql

一般来说为了更好的功能和性能,都需要将软件升级到最新的版本,然而在开源软件中,由于一些开发商变化或其他的问题(开源授权变化),致使人们不愿使用最新的版本,一个最典型的问题就是CentOS操作系统。还有...

大厂高频:讲一下MySQL主从复制

大家经常听说主从复制,那么主从复制的意义?能解决的问题有哪些?主从复制能解决的问题就是在我们平时开发的程序中操作数据库的时候,大多数的情况查询的操作大大超过了写的操作,也就说对数据库读取数据的压力比较...

MYSQL数据库的五大安全防护措施

以技术为基础的企业里最有价值的资产莫过于是客户或者其数据库中的产品信息了。因此,在这样的企业中,保证数据库免受外界攻击是数据库管理的重要环节。很多数据库管理员并没有实施什么数据库保护措施,只是因为觉得...

docker安装mysql

准备工作已安装Docker环境(官方安装文档)终端/命令行工具(Linux/macOS/WSL)步骤1:拉取MySQL镜像打开终端执行以下命令,拉取官方MySQL镜像(默认最新版本):d...

Zabbix监控系统系列之六:监控 mysql

zabbix监控mysql1、监控规划在创建监控项之前要尽量考虑清楚要监控什么,怎么监控,监控数据如何存储,监控数据如何展现,如何处理报警等。要进行监控的系统规划需要对Zabbix很了解,这里只是...

详解MySQL的配置文件及优化

#头条创作挑战赛#在Windows系统中,MySQL服务器启动时最先读取的是my.ini这个配置文件。在Linux系统中,配置文件为my.cnf,其路径一般为/etc/my.cnf或/etc/mysq...

Mysql 几个批处理执行脚本

学习mysql过程中,需要创建测试数据,并让多人每人一个数据库连接并进行作业检查。整合部分批处理创建数据批量创建数据库DELIMITER$CREATEPROCEDURECreateDatab...

MySQL学到什么程度?才有可以在简历上写精通

前言如今互联网行业用的最多就是MySQL,然而对于高级Web面试者,尤其对于寻找30k下工作的求职者,很多MySQL相关知识点基本都会涉及,如果面试中,你的相关知识答的模糊和不切要点,基...

mysql 主、从服务器配置“Slave_IO_Running: Connecting” 问题分析

#在进行mysql主、从服务器配置时,”SHOWSLAVESTATUS;“查看从库状态Slave_IO_Runing,出现错误:“Slave_IO_Running:Connectin...

MYSQL数据同步

java开发工程师在实际的开发经常会需要实现两台不同机器上的MySQL数据库的数据同步,要解决这个问题不难,无非就是mysql数据库的数据同步问题。但要看你是一次性的数据同步需求,还是定时数据同步,亦...

「MySQL 8」MySQL 5.7都即将停只维护了,是时候学习一波MySQL 8了

MySQL8新特性选择MySQL8的背景:MySQL5.6已经停止版本更新了,对于MySQL5.7版本,其将于2023年10月31日停止支持。后续官方将不再进行后续的代码维护。另外,...

Prometheus监控mysql

通过Prometheus监控Mysql,我们需要在Mysql端安装一个mysql-exporter,然后Prometheus通过mysql-exporter暴露的端口抓取数据。1.安装一个MYSQL配...