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;
:
- ?
- Nullable ( "Nullable reference types")?
- ( ), "" (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 , , , AutoMapper, Dapper . . ( ) ! , , , , , AutoMapper , , IL " ". , ( ). , , .