jdin043 发表于 2025-2-7 03:34:46

使用Roslyn的源生成器生成DTO

前言

源生成器的好处很多, 通过在编译时生成代码,可以减少运行时的反射和动态代码生成,从而提高应用程序的性能, 有时候需要对程序AOT以及裁剪编译的dll也是需要用SG来处理的。
我们开发程序应该都绕不过Mapper对象映射,用的比较多的库可能就是AutoMapper,Maspter之内的三方库吧;这些库很强大但是因为内部实现存在反射,因此开发的程序就没办法AOT了,因此如果程序不是很复杂但是又有很特殊的需求,建议使用SG来实现Mapper
功能演示

这里我演示下自己开发的AutoDto生成DTO功能:
比如我们有一个User的类,需要生成UserDto
public class User{        public string Id { get; set; } = null!;        public string FirstName { get; set; } = null!;        public string LastName { get; set; } = null!;        public int? Age { get; set; }        public string? FullName => $"{FirstName} {LastName}";}定义UserDto并标注特性:
//这里我们假设排除Id属性public partial record UserDto;就这样,源生成器将自动为我们生成对应的Dto:
partial record class UserDto{        /// <inheritdoc cref = "User.FirstName"/>        public string FirstName { get; set; }        /// <inheritdoc cref = "User.LastName"/>        public string LastName { get; set; }        /// <inheritdoc cref = "User.Age"/>        public int? Age { get; set; }}并同时为我们生成一个简单的Mapper扩展方法:
public static partial class UserToUserDtoExtentions{        /// <summary>        /// mapper to UserDto        /// </summary>        /// <returns></returns>        public static UserDto MapperToUserDto(this User model)        {                return new UserDto()                {                        FirstName = model.FirstName,                        LastName = model.LastName,                        Age = model.Age,                        FullName = model.FullName,                };        }}实现代码

static void GENDTO(Compilation compilation, ImmutableArray<SyntaxNode> nodes, SourceProductionContext context){        if (nodes.Length == 0) return;        StringBuilder envStringBuilder = new();        envStringBuilder.AppendLine("// <auto-generated />");        envStringBuilder.AppendLine("using System;");        envStringBuilder.AppendLine("using System.Collections.Generic;");        envStringBuilder.AppendLine("using System.Text;");        envStringBuilder.AppendLine("using System.Threading.Tasks;");        envStringBuilder.AppendLine("#pragma warning disable");        foreach (var nodeSyntax in nodes.AsEnumerable())        {                //Cast<ClassDeclarationSyntax>()                //Cast<RecordDeclarationSyntax>()                if (nodeSyntax is not TypeDeclarationSyntax node)                {                        continue;                }                //如果是Record类                var isRecord = nodeSyntax is RecordDeclarationSyntax;                //如果不含partial关键字,则不生成                if (!node.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword)))                {                        continue;                }                AttributeSyntax? attributeSyntax = null;                foreach (var attr in node.AttributeLists.AsEnumerable())                {                        var attrName = attr.Attributes.FirstOrDefault()?.Name.ToString();                        if (attrName?.IndexOf(AttributeValueMetadataNameDto, System.StringComparison.Ordinal) == 0)                        {                                attributeSyntax = attr.Attributes.First(x => x.Name.ToString().IndexOf(AttributeValueMetadataNameDto, System.StringComparison.Ordinal) == 0);                                break;                        }                }                if (attributeSyntax == null)                {                        continue;                }                //转译的Entity类名                var entityName = string.Empty;                string pattern = @"(?<=<)(?<type>\w+)(?=>)";                var match = Regex.Match(attributeSyntax.ToString(), pattern);                if (match.Success)                {                        entityName = match.Groups["type"].Value.Split(['.']).Last();                }                else                {                        continue;                }                var sb = new StringBuilder();                sb.AppendLine();                sb.AppendLine($"//generate {entityName}-{node.Identifier.ValueText}");                sb.AppendLine();                sb.AppendLine("namespace $ni");                sb.AppendLine("{");                sb.AppendLine("$namespace");                sb.AppendLine("$classes");                sb.AppendLine("}");                // sb.AppendLine("#pragma warning restore");                string classTemp = $"partial $isRecord $className{{ $body }}";                classTemp = classTemp.Replace("$isRecord", isRecord ? "record class" : "class");                {                        // 排除的属性                        List<string> excapes = [];                        if (attributeSyntax.ArgumentList != null)                        {                                for (var i = 0; i < attributeSyntax.ArgumentList!.Arguments.Count; i++)                                {                                        var expressionSyntax = attributeSyntax.ArgumentList.Arguments.Expression;                                        if (expressionSyntax.IsKind(SyntaxKind.InvocationExpression))                                        {                                                var name = (expressionSyntax as InvocationExpressionSyntax)!.ArgumentList.DescendantNodes().First().ToString();                                                excapes.Add(name.Split(['.']).Last());                                        }                                        else if (expressionSyntax.IsKind(SyntaxKind.StringLiteralExpression))                                        {                                                var name = (expressionSyntax as LiteralExpressionSyntax)!.Token.ValueText;                                                excapes.Add(name);                                        }                                }                        }                        var className = node.Identifier.ValueText;                        var rootNamespace = string.Empty;                        //获取文件范围的命名空间                        var filescopeNamespace = node.AncestorsAndSelf().OfType<FileScopedNamespaceDeclarationSyntax>().FirstOrDefault();                        if (filescopeNamespace != null)                        {                                rootNamespace = filescopeNamespace.Name.ToString();                        }                        else                        {                                rootNamespace = node.AncestorsAndSelf().OfType<NamespaceDeclarationSyntax>().Single().Name.ToString();                        }                        StringBuilder bodyBuilder = new();                        List<string> namespaces = [];                        StringBuilder bodyInnerBuilder = new();                        StringBuilder mapperBodyBuilder = new();                        bodyInnerBuilder.AppendLine();                        List<string> haveProps = [];                        // 生成属性                        void GenProperty(TypeSyntax @type)                        {                                var symbols = compilation.GetSymbolsWithName(@type.ToString(), SymbolFilter.Type);                                foreach (ITypeSymbol symbol in symbols.Cast<ITypeSymbol>())                                {                                        var fullNameSpace = symbol.ContainingNamespace.ToDisplayString();                                        // 命名空间                                        if (!namespaces.Contains(fullNameSpace))                                        {                                                namespaces.Add(fullNameSpace);                                        }                                        symbol.GetMembers().OfType<IPropertySymbol>().ToList().ForEach(prop =>                                                                                                                                                                   {                                                                                                                                                                           if (!excapes.Contains(prop.Name))                                                                                                                                                                           {                                                                                                                                                                                   // 如果存在同名属性,则不生成                                                                                                                                                                                   if (haveProps.Contains(prop.Name))                                                                                                                                                                                   {                                                                                                                                                                                           return;                                                                                                                                                                                   }                                                                                                                                                                                   haveProps.Add(prop.Name);                                                                                                                                                                                   //如果是泛型属性,则不生成                                                                                                                                                                                   if (prop.ContainingType.TypeParameters.Any(x => x.Name == prop.Type.Name))                                                                                                                                                                                   {                                                                                                                                                                                           return;                                                                                                                                                                                   }                                                                                                                                                                                   // prop:                                                                                                                                                                                   var raw = $"public {prop.Type.ToDisplayString()} {prop.Name} {{get;set;}}";                                                                                                                                                                                   // body:                                                                                                                                                                                   bodyInnerBuilder.AppendLine($"/// <inheritdoc cref=\"{@type}.{prop.Name}\" />");                                                                                                                                                                                   bodyInnerBuilder.AppendLine($"{raw}");                                                                                                                                                                                   // mapper:                                                                                                                                                                                   // 只有public的属性才能赋值                                                                                                                                                                                   if (prop.GetMethod?.DeclaredAccessibility == Accessibility.Public)                                                                                                                                                                                   {                                                                                                                                                                                           mapperBodyBuilder.AppendLine($"{prop.Name} = model.{prop.Name},");                                                                                                                                                                                   }                                                                                                                                                                           }                                                                                                                                                                   });                                }                        }                        // 生成属性:                        var symbols = compilation.GetSymbolsWithName(entityName, SymbolFilter.Type);                        var symbol = symbols.Cast<ITypeSymbol>().FirstOrDefault();                        //引用了其他库.                        if (symbol is null)                                continue;GenProperty(SyntaxFactory.ParseTypeName(symbol.MetadataName));                        // 生成父类的属性:                        INamedTypeSymbol? baseType = symbol.BaseType;                        while (baseType != null)                        {                                GenProperty(SyntaxFactory.ParseTypeName(baseType.MetadataName));                                baseType = baseType.BaseType;                        }                        var rawClass = classTemp.Replace("$className", className);                        rawClass = rawClass.Replace("$body", bodyInnerBuilder.ToString());                        // append:                        bodyBuilder.AppendLine(rawClass);                        string rawNamespace = string.Empty;                        namespaces.ForEach(ns => rawNamespace += $"using {ns};\r\n");                        var source = sb.ToString();                        source = source.Replace("$namespace", rawNamespace);                        source = source.Replace("$classes", bodyBuilder.ToString());                        source = source.Replace("$ni", rootNamespace);                        // 生成Mapper                        var mapperSource = MapperTemplate.Replace("$namespace", namespaces.First());                        mapperSource = mapperSource.Replace("$ns", rootNamespace);                        mapperSource = mapperSource.Replace("$baseclass", entityName);                        mapperSource = mapperSource.Replace("$dtoclass", className);                        mapperSource = mapperSource.Replace("$body", mapperBodyBuilder.ToString());                        // 合并                        source = $"{source}\r\n{mapperSource}";                        envStringBuilder.AppendLine(source);                }        }        envStringBuilder.AppendLine("#pragma warning restore");        var envSource = envStringBuilder.ToString();        // format:        envSource = envSource.FormatContent();        context.AddSource($"Biwen.AutoClassGenDtoG.g.cs", SourceText.From(envSource, Encoding.UTF8));}const string MapperTemplate = $@"namespace $namespace{{    using $ns ;    public static partial class $baseclassTo$dtoclassExtentions    {{      /// <summary>      /// mapper to $dtoclass      /// </summary>      /// <returns></returns>      public static $dtoclass MapperTo$dtoclass(this $baseclass model)      {{            return new $dtoclass()            {{                $body            }};      }}    }}}}";最后

以上代码就完成了整个源生成步骤,最后你可以使用我发布的nuget包体验:
<ItemGroup>   <PackageReference Include="Biwen.AutoClassGen.Attributes" Version="1.3.6" />   <PackageReference Include="Biwen.AutoClassGen" Version="1.5.4" PrivateAssets="all" /></ItemGroup>当然如果你对完整的实现感兴趣可以移步我的GitHub仓储,欢迎star https://github.com/vipwan/Biwen.AutoClassGen
本文版权归作者所有,转载请注明出处!
页: [1]
查看完整版本: 使用Roslyn的源生成器生成DTO