How to modify code before compilation?
Using Roslyn, I would like to modify my C# code before the actual compilation. For now, I will just need something like:
[MyAnotatedMethod]
public void MyMethod()
{
// method-body
}
And based on the annotation, I would like to inject some code at the beginning of the method, and at the end of the method.
I'm aware of PostSharp, but that's not what I would like.
Is this possible to do with Roslyn? And if yes, could you give me an example?
Solution 1:
Here is a quick and dirty way of doing what you want. It's based on one of the above comments, which points to SebbyLive. It is just a proof of concept, I wouldn't try to use it in production.
The basic idea is that you change the compiler of the project that you want to modify. And this changed compiler will do the code injection. So you'd need to write a new compiler (AopCompiler.exe) and set it as the build tool in your project.
Setting the AopCompiler.exe as the build tool is easy, in you project file, you'd need to add the following two lines:
<CscToolPath>$(SolutionDir)AopCompiler\bin\Debug</CscToolPath>
<CscToolExe>AopCompiler.exe</CscToolExe>
The AopCompiler should be a simple console application. This is doing the code modification and the compilation too. If you don't want to modify the source code, just build it, then the easiest way is to call the csc.exe yourself:
static void Main(string[] args)
{
var p = Process.Start(@"C:\Program Files (x86)\MSBuild\14.0\Bin\csc.exe",
string.Join(" ", args));
p.WaitForExit();
}
So if you set this up so far you'd have a normal build process, without the aspect weaving.
At this point, if you check out what is in the args
, you'll see that there is a file path to an .RSP file, which contains all the command line parameters for the csc.exe. Naturally, these parameters contain all the the .CS file names too. So you could parse this .RSP file and find all the .CS files, that are part of the compilation.
With the C# files in hand, the rewriting can be done with Roslyn. There are many tutorials on CSharpSyntaxRewriter
, for example here, and here. You'd need to write your custom CSharpSyntaxRewriter
, which checks for the given attribute, and then add the logging to the beginning of the found methods. Adding logging to the end of each method is a bit trickier, because there can be multiple exit points. To find those, you can use control flow analysis. The built-in Roslyn control flow analysis can give you exactly what you are after, the ExitPoints
property holds the set of statements inside a region that jump to locations outside the region.
To get the semantic model (and then do the CFG analysis) you can do something like this:
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
{
var semanticModel = _compilation.GetSemanticModel(node.SyntaxTree);
// semanticModel.AnalyzeControlFlow(node.Block)
return node;
}
Finally, to process each of the input files, your AopCompiler, you just simply have to call your rewriter's Visit
method on the root of the tree. That will produce the modified tree, which you can write out to a file. (Either you can modify the original file, or write the result to a new one, and change the .RSP file accordingly.)
Sorry for not providing a full working solution, but I hope, this is enough to get you started.
Solution 2:
As I pointed out in my comment it is not currently available. Although you could put something together using AOP techniques shown here with the Roslyn Scripting API to provide a very flexible solution.
The correct answer at this point in time is that the Roslyn team have an open proposal/issue to support it. Thanks to the .Net Foundation and Microsoft participating in Open Source you can read about this features development here:
https://github.com/dotnet/roslyn/issues/5561
Solution 3:
It is a little more dirty than using an Annotation, but blocking code out using Preprocessor Directives will allow you to configure how your code compiles based on flags.
http://www.codeproject.com/Articles/304175/Preprocessor-Directives-in-Csharp
Code in the #ifMyFlag in the below will only be compiled if MyFlag is defined
#define MyFlag
public void MyMethod()
{
#if MyFlag
// Flag conditional code
#endif
// method-body
}