本例子依然来自 https://github.com/dotnet/roslyn/wiki/Getting-Started-C%23-Syntax-Transformation , 看过的大神跳过.
修改源码的功能一般常见于各种插件,比如大名鼎鼎的VAX, 本程序的作用也能修改源码, 而且就是修改自己刚打入的代码, 本project里包含两个cs文件, 运行之后会把这两个cs里的部分变量定义改为var. 比如Main函数的这几行, 修改前:
private static void Main()
{
SemanticModel model = test.GetSemanticModel(sourceTree);
TypeInferenceRewriter rewriter = new TypeInferenceRewriter(model);
SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());
}
修改后
private static void Main()
{
var model = test.GetSemanticModel(sourceTree);
var rewriter = new TypeInferenceRewriter(model);
var newSource = rewriter.Visit(sourceTree.GetRoot());
}
好吧, 把完整源码贴出来, 操作步骤跟例子1一样, 在vs2019里先建立 Stand-Alone Code Analysis Tool 工程,再删除只留下空的Main, 确保空Main函数运行不出错的前提下做修改. 建立工程时,项目名称叫TransformationCS, Main函数所在文件为Program.cs(默认),再建立一个新类名字是: TypeInferenceRewriter , 类文件名字TypeInferenceRewriter.cs, 这两个文件名字程序会用到, 所以要提前说清楚不要修改, 先贴出Program.cs完整内容
using System;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace TransformationCS
{
internal class Program
{
private static void Main()
{
Compilation test = CreateTestCompilation();
foreach (SyntaxTree sourceTree in test.SyntaxTrees)
{
SemanticModel model = test.GetSemanticModel(sourceTree);
TypeInferenceRewriter rewriter = new TypeInferenceRewriter(model);
SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());
if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
}
}
private static Compilation CreateTestCompilation()
{
String programPath = @"..\..\..\Program.cs";
String programText = File.ReadAllText(programPath);
SyntaxTree programTree =
CSharpSyntaxTree.ParseText(programText)
.WithFilePath(programPath);
String rewriterPath = @"..\..\..\TypeInferenceRewriter.cs";
String rewriterText = File.ReadAllText(rewriterPath);
SyntaxTree rewriterTree =
CSharpSyntaxTree.ParseText(rewriterText)
.WithFilePath(rewriterPath);
SyntaxTree[] sourceTrees = { programTree, rewriterTree };
MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location);
MetadataReference[] references = { mscorlib, codeAnalysis, csharpCodeAnalysis };
return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(
OutputKind.ConsoleApplication));
}
}
}
TypeInferenceRewriter.cs 完整内容
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace TransformationCS
{
public class TypeInferenceRewriter : CSharpSyntaxRewriter
{
private readonly SemanticModel SemanticModel;
public TypeInferenceRewriter(SemanticModel semanticModel)
{
this.SemanticModel = semanticModel;
}
public override SyntaxNode VisitLocalDeclarationStatement(
LocalDeclarationStatementSyntax node)
{
if (node.Declaration.Variables.Count > 1)
{
return node;
}
if (node.Declaration.Variables[0].Initializer == null)
{
return node;
}
VariableDeclaratorSyntax declarator = node.Declaration.Variables.First();
TypeSyntax variableTypeName = node.Declaration.Type;
ITypeSymbol variableType =
(ITypeSymbol)SemanticModel.GetSymbolInfo(variableTypeName)
.Symbol;
TypeInfo initializerInfo =
SemanticModel.GetTypeInfo(declarator
.Initializer
.Value);
if (variableType == initializerInfo.Type)
{
TypeSyntax varTypeName =
IdentifierName("var")
.WithLeadingTrivia(
variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(
variableTypeName.GetTrailingTrivia());
return node.ReplaceNode(variableTypeName, varTypeName);
}
else
{
return node;
}
}
}
}
运行时要留意文件路径问题, 否则提示找不到 , 就是这两行,
String programPath = @"..\..\..\Program.cs";
String rewriterPath = @"..\..\..\TypeInferenceRewriter.cs";
我把它从 ..\..\ 改成 ..\..\..\ , 因为我的exe文件在debug目录下还有一级, 实际是 \TransformationCS\TransformationCS\bin\Debug\net472 , 所以要做修改, 可能vs2015里没有net472这一级(我没测试过), 所以原作者用了2级目录. 如果实在搞不清, 建议修改为绝对路径. 程序运行完毕后, 就会发现刚写的代码里面有部分变量类型被改为var.
可以用ctrl+z / ctrl+y 来回切换对比看结果. 修改的核心功能就在TypeInferenceRewriter类的VisitLocalDeclarationStatement 里面,
在第一篇Roslyn入门(1):一个简单修改C#代码的例子说过, 这个VisitLocalDeclarationStatement 重载是关于变量声明语句的, 所以传进来的node全是变量声明语句, 变量个数超过1个和没初始化的不管, 其他酌情处理.