Code generation using Roslyn can be used without switching to .Net 5







Recently, when I was browsing the new features that will be included in .Net 5, I came across one very interesting one - source code generators. I was particularly interested in this functionality, since I have been using a similar approach for the last ... 5 years, and what Microsoft offers is simply a deeper integration of this approach into the project build process.







: , .Net 5 - , , , , - , Roslyn .







Roslyn , , , Microsoft .Net 5 .







, . JSON - REST .Net ( ) - , , DTO, REST .







, - , , , - .







, , . 100 , SQL, (visitors — IVisitor ), ( " LINQ SQL").







, , , , , (visitors), . , (assembly), , , , , .







, , , , Roslyn - , , , .







Microsoft.CodeAnalysis.CSharp.







: t4 ( ), dll , .







, .cs , , :







var files = Directory.EnumerateFiles(
    Path.Combine(projectFolder, "Syntax"), 
    "*.cs", 
    SearchOption.AllDirectories);

files = files.Concat(Directory.EnumerateFiles(projectFolder, "IExpr*.cs"));

var trees = files
    .Select(f => CSharpSyntaxTree.ParseText(File.ReadAllText(f)))
    .ToList();
      
      





( , . .), , , , Roslyn , :







var cSharpCompilation = CSharpCompilation.Create("Syntax", trees);

foreach (var tree in trees)
{
    var semantic = cSharpCompilation.GetSemanticModel(tree);
    ...
      
      





, INamedTypeSymbol:







foreach (var classDeclarationSyntax in tree
    .GetRoot()
    .DescendantNodesAndSelf()
    .OfType<ClassDeclarationSyntax>())
{
    var classSymbol = semantic.GetDeclaredSymbol(classDeclarationSyntax);
      
      





:







//Properties
var properties = GetProperties(classSymbol);

List<ISymbol> GetProperties(INamedTypeSymbol symbol)
{
    List<ISymbol> result = new List<ISymbol>();
    while (symbol != null)
    {
        result.AddRange(symbol.GetMembers()
            .Where(m => m.Kind == SymbolKind.Property));
        symbol = symbol.BaseType;
    }

    return result;
}

//Constructors
foreach (var constructor in classSymbol.Constructors)
{
    ...
}
      
      





, , :







foreach (var parameter in constructor.Parameters)
{
    ...
    INamedTypeSymbol pType = (INamedTypeSymbol)parameter.Type;
      
      





:







  1. ?
  2. Nullable ( "Nullable reference types")?
  3. ( ), "" (Visitors).


:







var ta = AnalyzeSymbol(ref pType);
....
(bool IsNullable, bool IsList, bool Expr) AnalyzeSymbol(
    ref INamedTypeSymbol typeSymbol)
{
    bool isList = false;
    var nullable = typeSymbol.NullableAnnotation == NullableAnnotation.Annotated;

    if (nullable && typeSymbol.Name == "Nullable")
    {
        typeSymbol = (INamedTypeSymbol)typeSymbol.TypeArguments.Single();
    }

    if (typeSymbol.IsGenericType)
    {
        if (typeSymbol.Name.Contains("List"))
        {
            isList = true;
        }

        if (typeSymbol.Name == "Nullable")
        {
            nullable = true;
        }

        typeSymbol = (INamedTypeSymbol)typeSymbol.TypeArguments.Single();
    }

    return (nullable, isList, IsExpr(typeSymbol));
}
      
      





: AnalyzeSymbol Nullables::







List<T> => T (list := true) 
T? => T (nullable := true) 
List<T>? => T (list := true, nullable := true)
      
      





Roslyn , , :







bool IsExpr(INamedTypeSymbol symbol)
{
    while (symbol != null)
    {
        if (symbol.Interfaces.Any(NameIsExpr))
        {
            return true;
        }
        symbol = symbol.BaseType;
    }

    return false;

    bool NameIsExpr(INamedTypeSymbol iSym)
    {
        if (iSym.Name == "IExpr")
        {
            return true;
        }

        return IsExpr(iSym);
    }
}
      
      





:







public class NodeModel
{
    ...
    public string TypeName { get; }
    public bool IsSingleton { get; }
    public IReadOnlyList<SubNodeModel> SubNodes { get; }
    public IReadOnlyList<SubNodeModel> Properties { get; }
}

public class SubNodeModel
{
    ...
    public string PropertyName { get; }
    public string ConstructorArgumentName { get; }
    public string PropertyType { get; }
    public bool IsList { get; }
    public bool IsNullable { get; }
}
      
      





, - ( ). .







, .Net 5 ISourceGenerator Generator. . , , , , .







: .Net 5 , 1 2







, , .Net 5 , , , AutoMapper, Dapper . . ( ) ! , , , , , AutoMapper , , IL " ". , ( ). , , .







github








All Articles