面试题答案
一键面试整体设计方案
- 分析阶段:利用Roslyn的语法分析和语义分析功能,定位所有的public方法。
- 转换阶段:在语法树层面,对定位到的public方法进行代码注入。
- 生成阶段:确保注入后的代码能正确编译生成目标程序集。
关键类和接口
- SyntaxWalker:用于遍历语法树,定位public方法。通过继承
SyntaxWalker
类,可以自定义遍历逻辑。 - SyntaxRewriter:用于修改语法树,在public方法中注入代码。继承
SyntaxRewriter
类,并重写相关方法实现代码注入。 - Compilation:代表整个编译过程,可用于获取语法树、生成编译结果等。
- CSharpCompilation:C# 语言特定的编译表示,继承自
Compilation
。
具体实现步骤
- 创建语法分析器:
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
- 创建语义模型:
var references = new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }; var compilation = CSharpCompilation.Create("MyCompilation", new[] { syntaxTree }, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); var semanticModel = compilation.GetSemanticModel(syntaxTree);
- 遍历语法树定位public方法:
public class PublicMethodWalker : SyntaxWalker { public List<MethodDeclarationSyntax> PublicMethods { get; } = new List<MethodDeclarationSyntax>(); public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { if (node.Modifiers.Any(SyntaxKind.PublicKeyword)) { PublicMethods.Add(node); } base.VisitMethodDeclaration(node); } } var walker = new PublicMethodWalker(); walker.Visit(syntaxTree.GetRoot());
- 注入日志记录代码:
public class LoggingRewriter : SyntaxRewriter { public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) { if (node.Modifiers.Any(SyntaxKind.PublicKeyword)) { var logStatement = SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( SyntaxFactory.IdentifierName("Console.WriteLine"), SyntaxFactory.ArgumentList( SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Argument(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("Entering method: " + node.Identifier.ValueText))) })))); var block = node.Body ?? SyntaxFactory.Block(); var newBlock = SyntaxFactory.Block(logStatement, block); return node.WithBody(newBlock); } return base.VisitMethodDeclaration(node); } } var rewriter = new LoggingRewriter(); var newSyntaxTree = rewriter.Visit(syntaxTree.GetRoot()).SyntaxTree;
- 重新编译:
var newCompilation = compilation.ReplaceSyntaxTree(syntaxTree, newSyntaxTree); using (var ms = new MemoryStream()) { var result = newCompilation.Emit(ms); if (result.Success) { ms.Seek(0, SeekOrigin.Begin); // 加载并执行新生成的程序集 } else { foreach (var diagnostic in result.Diagnostics) { Console.WriteLine(diagnostic); } } }