preamble
The benefits of a source generator are many, by generating code at compile time, you can improve the performance of your application by reducing runtime reflection and dynamic code generation, which is sometimes required for programsAOT
As well as trimming compiled dll's is also something that needs to be handled with SG.
We should not be able to get around the Mapper object mapping in our development programs, and the libraries that we use more often are probably theAutoMapper
,Maspter
These libraries are very powerful, but because of the internal implementation of reflection, there is no way to develop a program that can be used as an interface to a library.AOT
Therefore, if the program is not very complex but has very special needs, it is recommended to use SG to implement Mapper.
Function Demo
Here I demo my own developedAutoDto
Generate DTO Functions.
For example, if we have a User class, we need to generate a 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}";
}
Define UserDto and label the properties:.
[AutoDto<User>(nameof())]//Here we are assuming that we exclude the Id attribute
public partial record UserDto.
That's it, the source generator will generate the corresponding Dto for us.
partial record class UserDto
{
/// <inheritdoc cref = ""/>
public string FirstName { get; set; }
/// <inheritdoc cref = ""/>
public string LastName { get; set; }
/// <inheritdoc cref = ""/>
public int? Age { get; set; }
/// <inheritdoc cref = ""/>
}
and also generates a simple Mapper extension method for us: the
public static partial class UserToUserDtoExtentions
{
/// <summary>
/// mapper to UserDto
/// </summary>
/// <returns></returns>
public static UserDto MapperToUserDto(this User model)
{
return new UserDto()
{
FirstName = ,
LastName = ,
Age = ,
FullName = ,
};
}
}
Implementation Code
static void GENDTO(Compilation compilation, ImmutableArray<SyntaxNode> nodes, SourceProductionContext context)
{
if ( == 0) return;
StringBuilder envStringBuilder = new();
("// <auto-generated />");
("using System;");
("using ;");
("using ;");
("using ;");
("#pragma warning disable");
foreach (var nodeSyntax in ())
{
//Cast<ClassDeclarationSyntax>()
//Cast<RecordDeclarationSyntax>()
if (nodeSyntax is not TypeDeclarationSyntax node)
{
continue;
}
//If it isRecordresemble
var isRecord = nodeSyntax is RecordDeclarationSyntax;
//If notpartialKeywords.,then it does not generate
if (!(x => ()))
{
continue;
}
AttributeSyntax? attributeSyntax = null;
foreach (var attr in ())
{
var attrName = ()?.();
if (attrName?.IndexOf(AttributeValueMetadataNameDto, ) == 0)
{
attributeSyntax = (x => ().IndexOf(AttributeValueMetadataNameDto, ) == 0);
break;
}
}
if (attributeSyntax == null)
{
continue;
}
//freestandingEntityresemble名
var entityName = ;
string pattern = @"(?<=<)(?<type>\w+)(?=>)";
var match = ((), pattern);
if ()
{
entityName = ["type"].(['.']).Last();
}
else
{
continue;
}
var sb = new StringBuilder();
();
($"//generate {entityName}-{}");
();
("namespace $ni");
("{");
("$namespace");
("$classes");
("}");
// ("#pragma warning restore");
string classTemp = $"partial $isRecord $className {{ $body }}";
classTemp = ("$isRecord", isRecord ? "record class" : "class");
{
// Excluded Properties
List<string> excapes = [];
if ( != null)
{
for (var i = 0; i < !.; i++)
{
var expressionSyntax = [i].Expression;
if (())
{
var name = (expressionSyntax as InvocationExpressionSyntax)!.().First().ToString();
((['.']).Last());
}
else if (())
{
var name = (expressionSyntax as LiteralExpressionSyntax)!.;
(name);
}
}
}
var className = ;
var rootNamespace = ;
//Get the namespace of the file range
var filescopeNamespace = ().OfType<FileScopedNamespaceDeclarationSyntax>().FirstOrDefault();
if (filescopeNamespace != null)
{
rootNamespace = ();
}
else
{
rootNamespace = ().OfType<NamespaceDeclarationSyntax>().Single().();
}
StringBuilder bodyBuilder = new();
List<string> namespaces = [];
StringBuilder bodyInnerBuilder = new();
StringBuilder mapperBodyBuilder = new();
();
List<string> haveProps = [];
// Generating Properties
void GenProperty(TypeSyntax @type)
{
var symbols = (@(), );
foreach (ITypeSymbol symbol in <ITypeSymbol>())
{
var fullNameSpace = ();
// namespace (computing)
if (!(fullNameSpace))
{
(fullNameSpace);
}
().OfType<IPropertySymbol>().ToList().ForEach(prop =>
{
if (!())
{
// If an attribute with the same name exists,then it does not generate
if (())
{
return;
}
();
//If it is泛型属性,then it does not generate
if ((x => == ))
{
return;
}
// prop:
var raw = $"public {()} {} {{get;set;}}";
// body:
($"/// <inheritdoc cref=\"{@type}.{}\" />");
($"{raw}");
// mapper:
// only ifpublicin order to assign a value to an attribute of
if (?.DeclaredAccessibility == )
{
($"{} = model.{},");
}
}
});
}
}
// Generating Properties:
var symbols = (entityName, );
var symbol = <ITypeSymbol>().FirstOrDefault();
//References to other libraries.
if (symbol is null)
continue;
GenProperty(());
// generating父resemble的属性:
INamedTypeSymbol? baseType = ;
while (baseType != null)
{
GenProperty(());
baseType = ;
}
var rawClass = ("$className", className);
rawClass = ("$body", ());
// append:
(rawClass);
string rawNamespace = ;
(ns => rawNamespace += $"using {ns};\r\n");
var source = ();
source = ("$namespace", rawNamespace);
source = ("$classes", ());
source = ("$ni", rootNamespace);
// generatingMapper
var mapperSource = ("$namespace", ());
mapperSource = ("$ns", rootNamespace);
mapperSource = ("$baseclass", entityName);
mapperSource = ("$dtoclass", className);
mapperSource = ("$body", ());
// incorporation
source = $"{source}\r\n{mapperSource}";
(source);
}
}
("#pragma warning restore");
var envSource = ();
// format:
envSource = ();
($"", (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
}};
}}
}}
}}
";
ultimate
The above code completes the entire source generation step, and finally you can use the nuget package I released to experience.
<ItemGroup>
<PackageReference Include="" Version="1.3.6" />
<PackageReference Include="" Version="1.5.2" PrivateAssets="all" />
</ItemGroup>
Of course if you're interested in the full implementation you can move to my GitHub repository, welcome star!/vipwan/