Ben*_*jol 22 c# code-generation roslyn
(我要做的是通过从vs生成的设置文件生成接口和包装类来解决Application.Settings/MVVM问题.)
我想做的是:
我的问题是双重的:
如果你想评论MVVM方面,你可以,但这不是问题的主旨:)
svi*_*ick 13
如果您的要求是解析C#源代码,那么我认为Roslyn是一个不错的选择.如果您打算将它用于此部分,我认为将它用于代码生成也是有意义的.
使用Roslyn生成代码可能非常冗长(特别是与CodeDom相比时),但我认为这对您来说不是一个大问题.
我认为SyntaxRewriter
最适合在代码中进行本地化更改.但是你要问的是解析整个类并基于它生成类型,我认为,直接查询语法树最有效.
例如,为类中的所有属性生成只读接口的最简单示例可能如下所示:
var originalClass =
compilationUnit.DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
string originalClassName = originalClass.Identifier.ValueText;
var properties =
originalClass.DescendantNodes().OfType<PropertyDeclarationSyntax>();
var generatedInterface =
SyntaxFactory.InterfaceDeclaration('I' + originalClassName)
.AddMembers(
properties.Select(
p =>
SyntaxFactory.PropertyDeclaration(p.Type, p.Identifier)
.AddAccessorListAccessors(
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))))
.ToArray());
Run Code Online (Sandbox Code Playgroud)
我认为罗斯林是解决这个问题的好方法.关于我将使用Roslyn的哪个部分 - 我可能会使用SyntaxWalker
原始类,然后使用Fluent API为SyntaxNodes
您要生成的新类型构建新的.您可以在生成的代码中重用原始树的某些部分(例如,参数列表等).
这可能是一个简单的示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;
using Roslyn.Services.CSharp;
class Program
{
static void Main(string[] args)
{
var syntaxTree = SyntaxTree.ParseText(@"
class C
{
internal void M(string s, int i)
{
}
}");
}
}
class Walker : SyntaxWalker
{
private InterfaceDeclarationSyntax @interface = Syntax.InterfaceDeclaration("ISettings");
private ClassDeclarationSyntax wrapperClass = Syntax.ClassDeclaration("SettingsWrapper")
.WithBaseList(Syntax.BaseList(
Syntax.SeparatedList<TypeSyntax>(Syntax.ParseTypeName("ISettings"))));
private ClassDeclarationSyntax @class = Syntax.ClassDeclaration("SettingsClass")
.WithBaseList(Syntax.BaseList(
Syntax.SeparatedList<TypeSyntax>(Syntax.ParseTypeName("ISettings"))));
public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
{
var parameters = node.ParameterList.Parameters.ToArray();
var typeParameters = node.TypeParameterList.Parameters.ToArray();
@interface = @interface.AddMembers(
Syntax.MethodDeclaration(node.ReturnType, node.Identifier.ToString())
.AddParameterListParameters(parameters)
.AddTypeParameterListParameters(typeParameters));
// More code to add members to the classes too.
}
}
Run Code Online (Sandbox Code Playgroud)
关于代码生成的问题,我的建议是实际使用内联代码片段(使用解析CSharpSyntaxTree.ParseText
)和手动生成的组合SyntaxNodes
,但强烈偏好前者.我过去也使用过T4,但由于普遍缺乏集成和功能,我正在远离它们.
SyntaxNodes
如果多数是程序性的,文本可能变得难以阅读.SyntaxFactory
API提供有效语法的指导.CSharpParseOptions.Default.WithKind(SourceCodeKind.Script)
以获取正确的语法节点.GlobalStatementSyntax
,然后Statement
以a的身份访问该属性BlockSyntax
.使用帮助器方法解析单个SyntaxNodes
:
private static TSyntax ParseText<TSyntax>(string code, bool asScript = false)
{
var options = asScript
? CSharpParseOptions.Default.WithKind(SourceCodeKind.Script)
: CSharpParseOptions.Default;
var syntaxNodes =
CSharpSyntaxTree.ParseText(code, options)
.GetRoot()
.ChildNodes();
return syntaxNodes.OfType<TSyntax>().First();
}
Run Code Online (Sandbox Code Playgroud)SyntaxNodes
手动构建时,您通常需要进行最后的调用,SyntaxTree.NormalizeWhitespace(elasticTrivia: true)
以使代码"可以绕过".SyntaxNode.ToFullString()
来获取实际的代码文本,包括琐事.SyntaxTree.WithFilePath()
作为存储,当你来到写出来的代码最终的文件名一个方便的地方.CompilationUnitSyntaxs
.Formatter.Format
作为最后的步骤之一.