Back in 2004, I was doing some code-generation work as part of the OBIWAN project. When I started, CodeDom was being used to do the work, but I really didn’t like it because it made the generator code very hard to read and modify. Realistically, I would not need to support any other language than C#, so I started looking for alternatives. CodeSmith was very popular at the time for generating type-safe collections (.NET 2.0 generics didn’t exist yet), but it was targeted at one-shot generations, and not at creating code generation code. Then I found a very simple tool named CodeGen that appealed to me. I had been playing around with the Boost Preprocessor library recently, so I really liked the idea of using the preprocessor. I did a few tweaks to it and was able to use it for my needs at the time. Later on, around mid-2007, I needed to do code-generation again, so I took this tool and added a good amount of more power to it. At this time, it was very far apart from the original code, so I re-baptized it as PreSharp and published it to CodePlex. I never got around to do any documentation for it, so I’m making this post to try to compensate for that. I also moved the project recently from CodePlex to GitHub.
PreSharp as a C# Preprocessor
Let’s say we’re using .NET 2.0 which doesn’t have the Func<> delegates and we want to define our own:
1 2 3 4
This gets tedious really quickly, so let’s use PreSharp to generate this for us:
1 2 3 4 5 6 7 8 9
Now if we run PreSharp.exe <file.cs>, that region of code will be turned into this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
So, the syntax is similar to ASP.NET, similarly to most template based code generators: code between <% and %> is executed; code between <%= and %> is evaluated as an expression; and the rest of the code is dumped to the output directly.
PreSharp as a Source Code Generator Generator
The real power of PreSharp comes when instead of PRESHARP_TEMPLATE we use PRESHARP_TEMPLATE_LIBRARY. In this case the output after running PreSharp will be the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
So if we use this in a context were we have an object named writer with a method Write that accepts a string, it will generate the code for generating the code at runtime. It’s ugly code, but it does the job, and we can collapse all the #region PreSharp Generated blocks. If instead of writer, we have a variable named something else, we can suffix the #if directive with _variableName. For example, if we want output as the variable name, we’ll use PRESHARP_TEMPLATE_LIBRARY_output.
Visual Studio Integration
To setup the Visual Studio integration of PreSharp we just need to run it once without parameters. It will copy itself to C:\Program Files (x86)\PreSharp, customize MSBuild Custom.After.Microsoft.Common.targets and set some Visual Studio settings in the registry.
After this is setup, instead of running PreSharp.exe manually, we can just change the build action of the relevant files in Visual Studio from Compile to PreSharpInplace. This will make sure PreSharp is run before the compilation phase.
When there is any error in the code inside a PreSharp block, the errors will be displayed in the Visual Studio errors pane, next to the compilation errors, and we can double click them to go to the correct location. In some cases PreSharp fails the column number by a few characters, but the line number is always right.
As this is implemented through MSBuild tasks, it will also work in command line builds. Some other tools use Custom Tools that only work inside Visual Studio (but which have other advantages, like being able to display generated files below the source file in Solution Explorer).
Each PreSharp block is compiled independently of each other. If we need to share code, we must put it in separate files and set the build action to PreSharpTemplateLibrary. Then all public classes of those files will be available inside normal PreSharp template blocks. Additionally, you can also use directives inside template blocks to import namespaces and to reference assemblies, by using <%@ Import Namespace="NamespaceToImport" %> and <%@ Assembly Name="AssemblyToReference" %>.
Let’s rewrite our example to take advantage of these features. We’ll add a file StringUtils.cs with the build action set to PreSharpTemplateLibrary:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
And then change the original PreSharp block to this:
1 2 3 4 5 6 7 8 9 10
In the Assembly directive you can specify the full path of an assembly (and in that case don’t forget to include the .dll at the end), or if that assembly is referenced by the Visual Studio project you can just specify the assembly name. PreSharp takes advantage of part of the MSBuild processing to find the file path.
In order for Visual Studio IntelliSense to work properly, StringUtils will be compiled into your assembly in debug builds, but it release builds it won’t, as this code is only meant to be used in your PreSharp blocks.
Instead of generating code in-place in a C# file, PreSharp can also be used to define code that will be called at compile time to output additional files to the project. For that, we will use a new build action - PreSharpTemplate. Here’s an example:
File ViewModelGenerator.cst with build action set to PreSharpTemplate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
File ViewModel.xml with build action set to PreSharpInput:
1 2 3 4 5 6 7 8 9
File ViewModelDefinition.cst with build action set to PreSharpTemplateInclude:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
Let me explain how this works:
- The file with the build action set to PreSharpTemplate must specify directive with the format <%@ EntryPoint Statement="codeToExecute();" %> to tell PreSharp what code to execute at compile time. That code can call PreSharp.SetOutput(string filename, PreSharp.OutputType outputType) inside the blocks to set the output file (OutputType is an enum with the values Compile, EmbeddedResource, and None). From that point on, the code generated by the PreSharp blocks will be written to that file, and that file will be added to the Visual Studio project with the corresponding build action.
- The build action PreSharpTemplateInclude and the associated <%@ Include Path="path" %> directive allow you to split generators in multiple files. If you just have multiple files with PreSharpTemplate build action they will all be handled independently.
- In the examples I uses the extension .cst but in reality it could be anything. When PreSharp is installed it registers .cst in Visual Studio as a c# file, so you can use that extension and still have syntax highlighting.
- The build action PreSharpInput informs PreSharp that whenever that file is changed, it should regenerate the target files.
- The <%@ CodeTemplate Language="C#" TargetLanguage="C#" %> directive is ignored, and it’s only supported to allow compatibility with CodeSmith so you can edit these files in its editor and have syntax highlighting.
When PreSharp executes the templates in the previous example, it generates a file named ViewModelGenerator.cst.PreSharpDebug.cs. If you want to debug the template execution, you can create a new C# project that includes this file, references C:\Program Files (x86)\PreSharp\PreSharp.exe as an assembly, and then put the contents of the Entry directive as the Main method. When running this project you can step by step the execution of the template.
I’ve been using PreSharp for a few years in real-life production software, so it’s pretty stable and efficient by now. But there is still one very annoying problem with it. If you have a generated file open and then build the project and that file is changed, Visual Studio will wrongly use the cache of the previous content of the file in the compilation, and your build output will be wrong. At OutSystems we call this the “double build syndrome”. If you don’t have that file open, or if you’re building directly with MSBuild on a command shell or on a build server, it all works correctly, so it’s really a Visual Studio bug.
Aside from this post (which I should have done three years ago when I first created the blog), there is currently no other documentation for PreSharp, so feel free to ask any question through a comment here or through the GitHub project page.