Dotnet code generation overview by example
Tags: dotnet, csharp, programming, codegeneration
Published: 2021-10-10 17:06:55

## Introduction

Code generation is a very interesting topic. Instead of just writing code you can write code to write code. You can do code generation at compile time (new fancy source generators) and at runtime (expressions, emit IL). Anyway the idea to create methods and classes at runtime sounds like a magic to me. Runtime code generation feature is used quite heavily under the hood of DI frameworks, ORMs, different types of object mappers etc. Now when I have a sufficient understanding of the topic I realized that in the past I had some tasks which could be solved in more efficient and elegant way by using code generation. Unfortunately during those times I knew nothing about it. Searching the internet gave me results with quite high entry threshold and they didn't give an entire understanding of the feature. Most of examples in articles are quite trivial so it's still unclear how to apply it in practice. Here as the first step I want to describe a particular problem which could be solved with [metaprogramming](https://en.wikipedia.org/wiki/Metaprogramming) and then to give an overview of different code generation approaches. There will be a lot of code.

## Task description

Let's imagine our application receives a data from some source as an array of strings (for simplicity only string, integer and datetime values are expected in an input array):
```json
["John McClane", "1994-11-05T13:15:30", "4455"]
```
I need a generic way to parse this input into the instance of a particular class. This is an interface to create a parser delegate (i.e. it accepts an array of strings as the input and returns an instance of `T` as the output):
```csharp
public interface IParserFactory
{
    Func<string[], T> GetParser<T>() where T : new();
}
```
I use `ParserOutputAttribute` to identify classes used as parser's output. And I use `ArrayIndexAttribute` to understand which property corresponds to each of the array elements:
```csharp
[ParserOutput]
public class Data
{
    [ArrayIndex(0)] public string Name { get; set; } // will be "John McClane"
    [ArrayIndex(2)] public int Number { get; set; } // will be 4455
    [ArrayIndex(1)] public DateTime Birthday { get; set; } // will be 1994-11-05T13:15:30
}
```
If array element can't be parsed to the target type then it's ignored.
So as a general idea I don't want to limit implementation by `Data` class only. I want to produce a parser delegate for any type with the proper attributes. 

## Plain C&#35;

First of all I want to write a plain C# code without code generation or reflection at all for a known type:
```csharp
var data = new Data();
if (0 < inputArray.Length)
{
    data.Name = inputArray[0];
}
if (1 < inputArray.Length && DateTime.TryParse(inputArray[1], out var bd))
{
    data.Birthday = bd;
}
if (2 < inputArray.Length && int.TryParse(inputArray[2], out var n))
{
    data.Number = n;
}
return data;
```
Quite simple, right? But now I want to generate the same code for an arbitrary type at runtime or compile time. Let's go!

## Reflection

In the first approach with [reflection](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection) I'm not going to generate a parser delegate. Instead I'm going to create an instance of the target type and set its properties using reflection API.
```csharp
public class ReflectionParserFactory : IParserFactory
{
	public Func<string[], T> GetParser<T>() where T : new()
	{
		return ArrayIndexParse<T>;
	}

	private static T ArrayIndexParse<T>(string[] data) where T : new()
	{
        // create a new instance of target type
		var instance = new T();
		var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);

        //go through all public and non-static properties
        //read and parse corresponding element in array and if success - set property value
		for (int i = 0; i < props.Length; i++)
		{
			var attrs = props[i].GetCustomAttributes(typeof(ArrayIndexAttribute)).ToArray();
			if (attrs.Length == 0) continue;

			int order = ((ArrayIndexAttribute)attrs[0]).Order;
			if (order < 0 || order >= data.Length) continue;

			if (props[i].PropertyType == typeof(string))
			{
				props[i].SetValue(instance, data[order]);
				continue;
			}

			if (props[i].PropertyType == typeof(int))
			{
				if (int.TryParse(data[order], out var intResult))
				{
					props[i].SetValue(instance, intResult);
				}

				continue;
			}

			if (props[i].PropertyType == typeof(DateTime))
			{
				if (DateTime.TryParse(data[order], out var dtResult))
				{
					props[i].SetValue(instance, dtResult);
				}
			}
		}
		return instance;
	}
}
```
It works and it's quite readable. But it's [slow](https://mattwarren.org/2016/12/14/Why-is-Reflection-slow/) (check [benchmarks](#benchmarks) section below too). If you want to call this code very often it could be an issue. I want to implement something more sophisticated using **real** code generation.

## Code generation

### Expression trees

From the [official documentation](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/):
>Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x < y. You can compile and run code represented by expression trees.

- [How to execute expression trees](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/how-to-execute-expression-trees)
- [Expression classes](https://docs.microsoft.com/en-us/dotnet/api/system.linq.expressions?view=netcore-3.1#classes)

Expression trees give primitive building blocks like `Expression.Call` to call a method, `Expression.Loop` to add some repeating logic etc. Then using these blocks we build a parser delegate as a `tree` of instructions and finally compile it into the delegate at runtime.
```csharp
public class ExpressionTreeParserFactory : IParserFactory
{
	public Func<string[], T> GetParser<T>() where T : new()
	{
		var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);

        //declare an input parameter of the delegate
		ParameterExpression inputArray = Expression.Parameter(typeof(string[]), "inputArray");
        //declare an output parameter of the delegate
		ParameterExpression instance = Expression.Variable(typeof(T), "instance");

        //create a new instance of target type
		var block = new List<Expression>
		{
			Expression.Assign(instance, Expression.New(typeof(T).GetConstructors()[0]))
		};
		var variables = new List<ParameterExpression> {instance};

        //go through all public and non-static properties
		foreach (var prop in props)
		{
			var attrs = prop.GetCustomAttributes(typeof(ArrayIndexAttribute)).ToArray();
			if (attrs.Length == 0) continue;

			int order = ((ArrayIndexAttribute)attrs[0]).Order;
			if (order < 0) continue;

            //validate an index from ArrayIndexAttribute
			var orderConst = Expression.Constant(order);
			var orderCheck = Expression.LessThan(orderConst, Expression.ArrayLength(inputArray));

			if (prop.PropertyType == typeof(string))
			{
                //set string property
				var stringPropertySet = Expression.Assign(
					Expression.Property(instance, prop),
					Expression.ArrayIndex(inputArray, orderConst));

				block.Add(Expression.IfThen(orderCheck, stringPropertySet));
				continue;
			}

            //get parser method from the list of available parsers (currently we parse only Int and DateTime)
			if (!TypeParsers.Parsers.TryGetValue(prop.PropertyType, out var parser))
			{
				continue;
			}

			var parseResult = Expression.Variable(prop.PropertyType, "parseResult");
			var parserCall = Expression.Call(parser, Expression.ArrayIndex(inputArray, orderConst), parseResult);
			var propertySet = Expression.Assign(
				Expression.Property(instance, prop),
				parseResult);

            //set property if an element of array is successfully parsed
			var ifSet = Expression.IfThen(parserCall, propertySet);

			block.Add(Expression.IfThen(orderCheck, ifSet));
			variables.Add(parseResult);
		}

		block.Add(instance);

        //compile lambda expression into delegate
		return Expression.Lambda<Func<string[], T>>(
			Expression.Block(variables.ToArray(), Expression.Block(block)), 
			inputArray).Compile();
	}
}
```

### Emit IL

Dotnet compiler transforms your C# code into intermediate language ([CIL or just IL](https://en.wikipedia.org/wiki/Common_Intermediate_Language)) and then dotnet runtime translates IL into machine instructions. For instance, using [sharplab.io](https://sharplab.io/) you can easily check how generated IL will look like:
![C# and corresponding IL from https://sharplab.io/](https://mtkachenko.me/blog/assets/sharplab_msil.png)
- [System.Reflection.Emit](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit?view=net-5.0)
- [How to: Define and Execute Dynamic Methods](https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/how-to-define-and-execute-dynamic-methods)
- [OpCodes list](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes?view=netcore-3.1#fields)
- [ReSharper IL viewer](https://www.jetbrains.com/help/resharper/Viewing_Intermediate_Language.html)

Here we are going to write ("emit") IL instructions directly and then compile them into the delegate at runtime.
```csharp
public class EmitIlParserFactory : IParserFactory
{
	public Func<string[], T> GetParser<T>() where T : new()
	{
		var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);

		var dm = new DynamicMethod($"from_{typeof(string[]).FullName}_to_{typeof(T).FullName}", 
			typeof(T), new [] { typeof(string[]) }, typeof(EmitIlParserFactory).Module);
		var il = dm.GetILGenerator();

        //create a new instance of target type
		var instance = il.DeclareLocal(typeof(T));
		il.Emit(OpCodes.Newobj, typeof(T).GetConstructors()[0]);
		il.Emit(OpCodes.Stloc, instance);

        //go through all public and non-static properties
		foreach (var prop in props)
		{
			var attrs = prop.GetCustomAttributes(typeof(ArrayIndexAttribute)).ToArray();
			if (attrs.Length == 0) continue;

			int order = ((ArrayIndexAttribute)attrs[0]).Order;
			if (order < 0) continue;

			var label = il.DefineLabel();

			if (prop.PropertyType == typeof(string))
			{
                //check whether order from ArrayIndexAttribute is a valid index of the input array
				il.Emit(OpCodes.Ldc_I4, order);
				il.Emit(OpCodes.Ldarg_0);
				il.Emit(OpCodes.Ldlen);
				il.Emit(OpCodes.Bge_S, label);

                //set string property
				il.Emit(OpCodes.Ldloc, instance);
				il.Emit(OpCodes.Ldarg_0);
				il.Emit(OpCodes.Ldc_I4, order);
				il.Emit(OpCodes.Ldelem_Ref);
				il.Emit(OpCodes.Callvirt, prop.GetSetMethod());

				il.MarkLabel(label);
				continue;
			}

            //get parser method from the list of available parsers (currently we parse only Int and DateTime)
			if (!TypeParsers.Parsers.TryGetValue(prop.PropertyType, out var parser))
			{
				continue;
			}

            //check whether order from ArrayIndexAttribute is a valid index of the input array
			il.Emit(OpCodes.Ldc_I4, order);
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldlen);
			il.Emit(OpCodes.Bge_S, label);

			var parseResult = il.DeclareLocal(prop.PropertyType);

			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldc_I4, order);
			il.Emit(OpCodes.Ldelem_Ref);
			il.Emit(OpCodes.Ldloca, parseResult);
			il.EmitCall(OpCodes.Call, parser, null);
			il.Emit(OpCodes.Brfalse_S, label);
            
            //set property if an element of array is successfully parsed
			il.Emit(OpCodes.Ldloc, instance);
			il.Emit(OpCodes.Ldloc, parseResult);
			il.Emit(OpCodes.Callvirt, prop.GetSetMethod());

			il.MarkLabel(label);
		}

		il.Emit(OpCodes.Ldloc, instance);
		il.Emit(OpCodes.Ret);

        //create delegate from il instructions
		return (Func<string[], T>)dm.CreateDelegate(typeof(Func<string[], T>));
	}
}
```

### Sigil

- [A fail-fast validating helper for .NET CIL generation](https://github.com/kevin-montrose/Sigil)

This approach is quite similar to the previous one, but now we use sigil which gives us a syntax sugar and more understandable error messages.
```csharp
public class SigilParserFactory : IParserFactory
{
	public Func<string[], T> GetParser<T>() where T : new()
	{
		var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);

		var il = Emit<Func<string[], T>>.NewDynamicMethod($"from_{typeof(string[]).FullName}_to_{typeof(T).FullName}");

		var instance = il.DeclareLocal<T>();
		il.NewObject<T>();
		il.StoreLocal(instance);

		foreach (var prop in props)
		{
			var attrs = prop.GetCustomAttributes(typeof(ArrayIndexAttribute)).ToArray();
			if (attrs.Length == 0) continue;

			int order = ((ArrayIndexAttribute)attrs[0]).Order;
			if (order < 0) continue;

			var label = il.DefineLabel();

			if (prop.PropertyType == typeof(string))
			{
				il.LoadConstant(order);
				il.LoadArgument(0);
				il.LoadLength<string>();
				il.BranchIfGreaterOrEqual(label);

				il.LoadLocal(instance);
				il.LoadArgument(0);
				il.LoadConstant(order);
				il.LoadElement<string>();
				il.CallVirtual(prop.GetSetMethod());

				il.MarkLabel(label);
				continue;
			}

			if (!TypeParsers.Parsers.TryGetValue(prop.PropertyType, out var parser))
			{
				continue;
			}

			il.LoadConstant(order);
			il.LoadArgument(0);
			il.LoadLength<string>();
			il.BranchIfGreaterOrEqual(label);

			var parseResult = il.DeclareLocal(prop.PropertyType);
			
			il.LoadArgument(0);
			il.LoadConstant(order);
			il.LoadElement<string>();
			il.LoadLocalAddress(parseResult);
			il.Call(parser);
			il.BranchIfFalse(label);

			il.LoadLocal(instance);
			il.LoadLocal(parseResult);
			il.CallVirtual(prop.GetSetMethod());

			il.MarkLabel(label);
		}

		il.LoadLocal(instance);
		il.Return();

		return il.CreateDelegate();
	}
}
```

### Cache compiled parsers

We have implemented three approaches to create a parser delegate: expression tree, emit IL and sigil. In all cases we have the same problem: `IParserFactory.GetParser` does a hard job (builiding an expression tree or emitting IL and then creating delegate) every time you call it. Solution is quite simple - just cache it:
```csharp
public class CachedParserFactory : IParserFactory
{
	private readonly IParserFactory _realParserFactory;
	private readonly ConcurrentDictionary<string, Lazy<object>> _cache;

	public CachedParserFactory(IParserFactory realParserFactory)
	{
		_realParserFactory = realParserFactory;
		_cache = new ConcurrentDictionary<string, Lazy<object>>();
	}

	public Func<string[], T> GetParser<T>() where T : new()
	{
		return (Func<string[], T>)(_cache.GetOrAdd($"aip_{_realParserFactory.GetType().FullName}_{typeof(T).FullName}", 
			new Lazy<object>(() => _realParserFactory.GetParser<T>(), LazyThreadSafetyMode.ExecutionAndPublication)).Value);
	}
}
```
Now we reuse compiled versions of delegates which is more efficient.

### Roslyn based approaches

Roslyn is a dotnet compiler platform which doesn't only compile code but gives an ability to do syntax analysis and to generate code.

#### Roslyn runtime code generation
- [Using Roslyn to build object to object mapper](https://gunnarpeipman.com/using-roslyn-to-build-object-to-object-mapper/amp/)

Roslyn approach is quite interesting because it gives an ability to write plain C# (as a string though) instead of writing IL instructions or combining  expression tree blocks:
```csharp
public static class RoslynParserInitializer
{
    public static IParserFactory CreateFactory()
    {
        //get all types marked with ParserOutputAttribute
        var targetTypes =
            (from a in AppDomain.CurrentDomain.GetAssemblies()
                from t in a.GetTypes()
                let attributes = t.GetCustomAttributes(typeof(ParserOutputAttribute), true)
                where attributes != null && attributes.Length > 0
                select t).ToArray();

        var typeNames = new List<(string TargetTypeName, string TargetTypeFullName, string TargetTypeParserName)>();
        var builder = new StringBuilder();
        builder.AppendLine(@"
using System;
using Parsers.Common;

public class RoslynGeneratedParserFactory : IParserFactory 
{");
        //go through all types
        foreach (var targetType in targetTypes)
        {
            var targetTypeName = targetType.Name;
            var targetTypeFullName = targetType.FullName;
            var targetTypeParserName = targetTypeName + "Parser";
            typeNames.Add((targetTypeName, targetTypeFullName, targetTypeParserName));

            //generate private parser method for each target type
            builder.AppendLine($"private static T {targetTypeParserName}<T>(string[] input)");

            builder.Append($@"
{{
var {targetTypeName}Instance = new {targetTypeFullName}();");

            var props = targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            
            //go through all properties of the target type
            foreach (var prop in props)
            {
                var attrs = prop.GetCustomAttributes(typeof(ArrayIndexAttribute)).ToArray();
                if (attrs.Length == 0) continue;

                int order = ((ArrayIndexAttribute)attrs[0]).Order;
                if (order < 0) continue;

                if (prop.PropertyType == typeof(string))
                {
                    builder.Append($@"
if({order} < input.Length)
{{
{targetTypeName}Instance.{prop.Name} = input[{order}];
}}
");
                }

                if (prop.PropertyType == typeof(int))
                {
                    builder.Append($@"
if({order} < input.Length && int.TryParse(input[{order}], out var parsed{prop.Name}))
{{
{targetTypeName}Instance.{prop.Name} = parsed{prop.Name};
}}
");
                }

                if (prop.PropertyType == typeof(DateTime))
                {
                    builder.Append($@"
if({order} < input.Length && DateTime.TryParse(input[{order}], out var parsed{prop.Name}))
{{
{targetTypeName}Instance.{prop.Name} = parsed{prop.Name};
}}
");
                }
            }

            builder.Append($@"
object obj = {targetTypeName}Instance;
return (T)obj;
}}");
        }

        builder.AppendLine("public Func<string[], T> GetParser<T>() where T : new() {");
        foreach (var typeName in typeNames)
        {
            builder.Append($@"
if (typeof(T) == typeof({typeName.TargetTypeFullName}))
{{
return {typeName.TargetTypeParserName}<T>;
}}
");
        }
        builder.AppendLine("throw new NotSupportedException();}");

        builder.AppendLine("}");

        var syntaxTree = CSharpSyntaxTree.ParseText(builder.ToString());

        //reference assemblies
        string assemblyName = Path.GetRandomFileName();
        var refPaths = new List<string> {
            typeof(Object).GetTypeInfo().Assembly.Location,
            typeof(Enumerable).GetTypeInfo().Assembly.Location,
            Path.Combine(Path.GetDirectoryName(typeof(GCSettings).GetTypeInfo().Assembly.Location), "System.Runtime.dll"),
            typeof(RoslynParserInitializer).GetTypeInfo().Assembly.Location,
            typeof(IParserFactory).GetTypeInfo().Assembly.Location,
            Path.Combine(Path.GetDirectoryName(typeof(GCSettings).GetTypeInfo().Assembly.Location), "netstandard.dll"),
        };
        refPaths.AddRange(targetTypes.Select(x => x.Assembly.Location));

        var references = refPaths.Select(r => MetadataReference.CreateFromFile(r)).ToArray();

        // compile dynamic code
        var compilation = CSharpCompilation.Create(
            assemblyName,
            syntaxTrees: new[] { syntaxTree },
            references: references,
            options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

        //compile assembly
        using (var ms = new MemoryStream())
        {
            var result = compilation.Emit(ms);

            //to get a proper errors
            if (!result.Success)
            {
                throw new Exception(string.Join(",", result.Diagnostics.Where(diagnostic =>
                    diagnostic.IsWarningAsError ||
                    diagnostic.Severity == DiagnosticSeverity.Error).Select(x => x.GetMessage())));
            }
            ms.Seek(0, SeekOrigin.Begin);

            // load assembly from memory
            var assembly = AssemblyLoadContext.Default.LoadFromStream(ms);

            var factoryType = assembly.GetType("RoslynGeneratedParserFactory");
            if (factoryType == null) throw new NullReferenceException("Roslyn generated parser type not found");

            //create an instance of freshly generated parser factory
            return (IParserFactory)Activator.CreateInstance(factoryType);
        }
    }
}
```

#### Source generator
- Overview of source generators from the [official documentation](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview)

Source generator gives a very interesting ability of building parser's delegate during the compilation step, i.e. in advance. So in that case we don't have any runtime overhead to build a parser delegate at the first time which is amazing:
```csharp
[Generator]
public class ParserSourceGenerator : ISourceGenerator
{
	public void Initialize(GeneratorInitializationContext context)
	{
		//uncomment to debug
		//System.Diagnostics.Debugger.Launch();
	}

	public void Execute(GeneratorExecutionContext context)
	{
		var compilation = context.Compilation;
		var parserOutputTypeSymbol = compilation.GetTypeByMetadataName("Parsers.Common.ParserOutputAttribute");
		var attributeIndexTypeSymbol = compilation.GetTypeByMetadataName("Parsers.Common.ArrayIndexAttribute");
		var typesToParse = new List<ITypeSymbol>();

		foreach (var syntaxTree in compilation.SyntaxTrees)
		{
			var semanticModel = compilation.GetSemanticModel(syntaxTree);

            //get all types marked with ParserOutputAttribute
			typesToParse.AddRange(syntaxTree.GetRoot()
				.DescendantNodesAndSelf()
				.OfType<ClassDeclarationSyntax>()
				.Select(x => semanticModel.GetDeclaredSymbol(x))
				.OfType<ITypeSymbol>()
				.Where(x => x.GetAttributes().Select(a => a.AttributeClass)
					.Any(b => b == parserOutputTypeSymbol)));
		}

		var typeNames = new List<(string TargetTypeName, string TargetTypeFullName, string TargetTypeParserName)>();
		var builder = new StringBuilder();
		builder.AppendLine(@"
using System;
using Parsers.Common;
namespace BySourceGenerator
{
public class Parser : IParserFactory 
{");

        //go through all types
		foreach (var typeSymbol in typesToParse)
		{
			var targetTypeName = typeSymbol.Name;
			var targetTypeFullName = GetFullName(typeSymbol);
			var targetTypeParserName = targetTypeName + "Parser";
			typeNames.Add((targetTypeName, targetTypeFullName, targetTypeParserName));
			builder.AppendLine($"private static T {targetTypeParserName}<T>(string[] input)");

			builder.Append($@"
{{
var {targetTypeName}Instance = new {targetTypeFullName}();");

			var props = typeSymbol.GetMembers().OfType<IPropertySymbol>();

            //go through all properties of the target type
			foreach (var prop in props)
			{
				var attr = prop.GetAttributes().FirstOrDefault(x => x.AttributeClass == attributeIndexTypeSymbol);
				if (attr == null || !(attr.ConstructorArguments[0].Value is int)) continue;

				int order = (int) attr.ConstructorArguments[0].Value;
				if (order < 0) continue;

				if (GetFullName(prop.Type) == "System.String")
				{
					builder.Append($@"
if({order} < input.Length)
{{
{targetTypeName}Instance.{prop.Name} = input[{order}];
}}
");
				}

				if (GetFullName(prop.Type) == "System.Int32")
				{
					builder.Append($@"
if({order} < input.Length && int.TryParse(input[{order}], out var parsed{prop.Name}))
{{
{targetTypeName}Instance.{prop.Name} = parsed{prop.Name};
}}
");
				}

				if (GetFullName(prop.Type) == "System.DateTime")
				{
					builder.Append($@"
if({order} < input.Length && DateTime.TryParse(input[{order}], out var parsed{prop.Name}))
{{
{targetTypeName}Instance.{prop.Name} = parsed{prop.Name};
}}
");
				}
			}

			builder.Append($@"
object obj = {targetTypeName}Instance;
return (T)obj;
}}");
		}

		builder.AppendLine("public Func<string[], T> GetParser<T>() where T : new() {");
		foreach (var typeName in typeNames)
		{
			builder.Append($@"
if (typeof(T) == typeof({typeName.TargetTypeFullName}))
{{
return {typeName.TargetTypeParserName}<T>;
}}
");
		}

		builder.AppendLine("throw new NotSupportedException();}");

		builder.AppendLine("}}");

		var src = builder.ToString();
		context.AddSource(
			"ParserGeneratedBySourceGenerator.cs",
			SourceText.From(src, Encoding.UTF8)
		);
	}

	private static string GetFullName(ITypeSymbol typeSymbol) =>
		$"{typeSymbol.ContainingNamespace}.{typeSymbol.Name}";
}
```

## Benchmarks

The post wouldn't be comprehensive without benchmarks. I would like to compare two things:
- warm up step, i.e. generation of parser;
- invocation of already generated parser.

Benchmarks are measured using [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet). `μs` - microsecond, `ns` - nanosecond, 1 μs = 1000 ns.

<pre><code>
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1237 (21H1/May2021Update)
Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.401
  [Host]     : .NET 5.0.10 (5.0.1021.41214), X64 RyuJIT
  DefaultJob : .NET 5.0.10 (5.0.1021.41214), X64 RyuJIT
</code></pre>

### Generation of parser
<table>
<thead><tr><th>  Method</th><th>  Mean</th><th> Error</th><th>StdDev</th><th>Gen 0</th><th>Gen 1</th><th>Gen 2</th><th>Allocated</th>
</tr>
</thead><tbody><tr><td>EmitIl</td><td>22.02 &mu;s</td><td>0.495 &mu;s</td><td>1.429 &mu;s</td><td>1.2817</td><td>0.6409</td><td>0.0305</td><td>5 KB</td>
</tr><tr><td>ExpressionTree</td><td>683.68 &mu;s</td><td>13.609 &mu;s</td><td>31.268 &mu;s</td><td>2.9297</td><td>0.9766</td><td>-</td><td>14 KB</td>
</tr><tr><td>Sigil</td><td>642.63 &mu;s</td><td>12.305 &mu;s</td><td>29.243 &mu;s</td><td>112.3047</td><td>-</td><td>-</td><td>460 KB</td>
</tr><tr><td>Roslyn</td><td>71,605.64 &mu;s</td><td>2,533.732 &mu;s</td><td>7,350.817 &mu;s</td><td>1000.0000</td><td>-</td><td>-</td><td>5,826 KB</td>
</tr></tbody></table>

### Invocation of parser
<table>
<thead><tr><th>   Method</th><th> Mean</th><th>Error</th><th>StdDev</th><th>Ratio</th><th>RatioSD</th><th>Gen 0</th><th>Allocated</th>
</tr>
</thead><tbody><tr><td>EmitIl</td><td>374.7 ns</td><td>7.75 ns</td><td>22.36 ns</td><td>1.02</td><td>0.08</td><td>0.0095</td><td>40 B</td>
</tr><tr><td>ExpressionTree</td><td>378.1 ns</td><td>7.56 ns</td><td>20.57 ns</td><td>1.03</td><td>0.08</td><td>0.0095</td><td>40 B</td>
</tr><tr><td>Reflection</td><td>13,625.0 ns</td><td>272.60 ns</td><td>750.81 ns</td><td>37.29</td><td>2.29</td><td>0.7782</td><td>3,256 B</td>
</tr><tr><td>Sigil</td><td>378.9 ns</td><td>7.69 ns</td><td>21.06 ns</td><td>1.03</td><td>0.07</td><td>0.0095</td><td>40 B</td>
</tr><tr><td>Roslyn</td><td>404.2 ns</td><td>7.55 ns</td><td>17.80 ns</td><td>1.10</td><td>0.07</td><td>0.0095</td><td>40 B</td>
</tr><tr><td>SourceGenerator</td><td>384.4 ns</td><td>7.79 ns</td><td>21.46 ns</td><td>1.05</td><td>0.08</td><td>0.0095</td><td>40 B</td>
</tr><tr><td>ManuallyWritten</td><td>367.8 ns</td><td>7.36 ns</td><td>15.68 ns</td><td>1.00</td><td>0.00</td><td>0.0095</td><td>40 B</td>
</tr></tbody></table>

All approaches besides direct usage of reflection give results almost identical to [manually written C# parser](#plain-c).

## Source code

Here is [github repository](https://github.com/MaximTkachenko/dotnet-code-generation-overview-by-example) with parser factories, unit tests and benchmarks.