A lot of frameworks and libraries in .NET use reflection to find classes, look for attributes, read properties etc. and with no doubt reflection is a powerful tool. It does however come at the price of performance. On the other hand static C# performs incredibly well when it can be fully compiled but no one really wants to take the time of writing fast boilerplate code over and over again.
The idea of CGbR (Code Generator beats Reflection) is to combine these two offering a reflection similar API but performing dynamic parsing operations in the pre-build stage. Using C# partial classes it generates a file <file_name>.Generated.cs that contains performance optimized non-dynamic C# methods and properties based on attributes and interfaces defined in the original class. This makes it the perfect choice for performance critical applications, limited hardware capabilties and embedded projects with AOT compilation.
Quick links:
The tool supports 3 modes of operation. It can run on a single file or a project/solution directory. The first one is meant to be used within VisualStudio as a custom tool for a single file and the others are used as pre-build events or build targets. Choice is made automatically based on the first argument. For filepathes the file mode is chosen and the others look for a ".csproj" or ".sln" file in the given path.
In the File Mode CGbR operators on a single file only. Instead of a configuration it requires a couple of arguments. The first
argument is obviously the file. Next cames the name of the parser and all following arguments are interpreted as generator names.
This might look like this: $ cgbr.exe Messages/MyMessage.cs Regex BinarySerializer
In the Project Mode CGbR operates on the entire directory recursively. Parsers and Generators are selected by a cgbr.json
config file with the following structure. This is the default mode activated by adding the nuget package. With each build the generated files are created and must be included into the project.
{
"Enabled": true,
"Mappings": [
{
"Extension": ".cs",
"Parser": "Regex"
}
],
"LocalGenerators": [
{
"Name": "BinarySerializer",
"IsEnabled": true
},
{
"Name": "JsonSerializer",
"IsEnabled": true
}
],
"GlobalGenerators": [
]
}
The perfect usage scenario and actually the origin of CGbR is serializing and deserializing objects. In the original project performance gains from generated static code over the original reflection API were somewhere between factor of 100 and 700. Sample code can be found in the Generator tests and you will also find benchmarks comparing the different serializers.
The binary DataContract serializer target generates code that maps single objects or object structure onto byte arrays. It has literally zero overhead by using the class definition as a scheme to determine which byte represents which property. The code was optimized over several iterations and will create serialize/deserialize objects of binary size of around 1500 bytes in a matter of less then 30 micro seconds.
Concept: Consider the following classes input for the serializer
[DataContract]
public partial class Root
{
[DataMember]
public int Id { get; set; }
[DataMember]
public ushort Number { get; set; }
[DataMember]
public Partial[] Partials { get; set; }
[DataMember]
public double Price { get; set; }
}
[DataContract]
public partial class Partial
{
[DataMember]
public byte Index { get; set; }
[DataMember]
public long BigValue { get; set; }
}
The resulting array would look like this:
Position | Property | Length |
---|---|---|
0 - 3 | Id | 4 |
4 - 5 | Number | 2 |
6 - 7 | Partials.Length | 2 |
8 | Partial[0].Index | 1 |
9 - 16 | Partial[0].BigValue | 8 |
17 | Partial[1].Index | 1 |
18 - 25 | Partial[1].BigValue | 8 |
... | ... | |
8 + n*9 | Price | 8 |
Another serializer is the JSON serializer. It is not build from scratch but rather builds on the popular Json.NET from Newtonsoft. While writing JSON is done directly it uses JsonReader classes to parse the string. It replaces the reflection serializer classes with generated serialize and deserialize methods. Please refer to the sample code and benchmarks for further information.
CGbR can also be used to generate dependency injection.
CGbR could also be used to generate XAML or Forms based on class definitions.
Detailed guides on how to write custom generators follow soon.