Delegates Generation

CSharp · Console

/Program.cs

using System;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Reflection.Emit;

public static class Programm
{
	const int rounds = 100000;
	
	public static void Main()
	{
		var boolPlain = new BoolPlain{ TypedValue = true };
		var boolLinqExp = new BoolValueLinqExp{ TypedValue = true};
		var boolReflectionEmit = new BoolValueReflectionEmit { TypedValue = true };
		
		BenchmarkTools.MeasurePerformance("Plain", () =>
		{
			for (var i = 0; i < rounds; i++)
			{
				boolPlain.As<int>(out _);
			}
		});
		
		BenchmarkTools.MeasurePerformance("LinqExp", () =>
		{
			for (var i = 0; i < rounds; i++)
			{
				boolLinqExp.As<int>(out _);
			}
		});
		
		// Uncomment for the exception
		BenchmarkTools.MeasurePerformance("ReflectionEmit", () =>
		{
			for (var i = 0; i < rounds; i++)
			{
				boolReflectionEmit.As<int>(out _);
			}
		});
		
	}
}

public class BoolPlain : ValuePlain<bool>
{
	private static readonly IReadOnlyDictionary<Type, Func<bool, object>> k_Conversions = new Dictionary<Type, Func<bool, object>>
	{
		{
			typeof(int),
			value => value ? 1 : 0
		},
		{
			typeof(float),
			value => value ? 1.0f : 0.0f
		},
		{
			typeof(double),
			value => value ? 1.0 : 0.0
		}
	};
	protected override IReadOnlyDictionary<Type, Func<bool, object>> Conversions => k_Conversions;
}

public abstract class ValuePlain<T> : IValue
{
	private T m_Value;
	public T TypedValue { get => m_Value; set => m_Value = value; }

	public bool As<TTarget>(out TTarget result)
	{
		if (m_Value is TTarget target)
		{
			result = target;
			return true;
		}

		if (Conversions.TryGetValue(typeof(TTarget), out var conversion))
		{
			result = (TTarget)conversion(TypedValue);
			return true;
		}

		if (k_DefaultConversions.TryGetValue(typeof(TTarget), out var defaultConversion))
		{
			result = (TTarget)defaultConversion(TypedValue);
			return true;
		}

		result = default;
		return false;
	}

	private static readonly IReadOnlyDictionary<Type, Func<T, object>> k_DefaultConversions = new Dictionary<Type, Func<T, object>>
	{
		{
			typeof(string),
			value => value.ToString()
		},
	};
	protected abstract IReadOnlyDictionary<Type, Func<T, object>> Conversions { get; }
}

public class BoolValueLinqExp : ValueLinqExp<bool>
{
	private static readonly IReadOnlyDictionary<Type, Delegate> k_Conversions = new Dictionary<Type, Delegate>
	{
		{typeof(int), CreateConverter<int>(v => v ? 1 : 0)},
		{typeof(float), CreateConverter<float>(v => v ? 1.0f : 0.0f)},
		{typeof(double), CreateConverter<double>(v => v ? 1.0 : 0.0)}	
	};
	
	protected override IReadOnlyDictionary<Type, Delegate> Conversions => k_Conversions;
}

public class BoolValueReflectionEmit : ValueLinqExp<bool>
{
	private static readonly IReadOnlyDictionary<Type, Delegate> k_Conversions = new Dictionary<Type, Delegate>
	{
		{typeof(int), CreateConverterWithEmit<int>(v => v ? 1 : 0)},
		{typeof(float), CreateConverterWithEmit<float>(v => v ? 1.0f : 0.0f)},
		{typeof(double), CreateConverterWithEmit<double>(v => v ? 1.0 : 0.0)}	
	};
	protected override IReadOnlyDictionary<Type, Delegate> Conversions => k_Conversions;
}

public abstract class ValueLinqExp<T> : IValue
{
	private T m_Value;
	public T TypedValue { get => m_Value; set => m_Value = value; }

	public bool As<TTarget>(out TTarget result)
	{
		if (m_Value is TTarget target)
		{
			result = target;
			return true;
		}

		if (Conversions.TryGetValue(typeof(TTarget), out var conversion) && conversion is Func<T, TTarget> converter)
		{
			result = converter(TypedValue);
			return true;
		}

		if (k_DefaultConversions.TryGetValue(typeof(TTarget), out var defaultConversion) && defaultConversion is Func<T, TTarget> defaultConverter)
		{
			result = defaultConverter(TypedValue);
			return true;
		}

		result = default;
		return false;
	}

	protected static Delegate CreateConverter<TOutput>(Func<T, TOutput> converter)
	{
		var input = Expression.Parameter(typeof(T), "input");
		var body = Expression.Invoke(Expression.Constant(converter), input);
		var lambda = Expression.Lambda(body, input);
		return lambda.Compile();
	}
	
	protected static Delegate CreateConverterWithEmit<TOutput>(Func<T, TOutput> converter)
    {
        var method = new DynamicMethod(
            "Convert_" + typeof(T).Name + "_To_" + typeof(TOutput).Name,
            typeof(TOutput),
            new[] { typeof(T) },
            typeof(ValueLinqExp<>).Module,
            true);

        var il = method.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0); // Load the input argument onto the stack
        il.EmitCall(OpCodes.Callvirt, converter.Method, null); // Call the provided converter function
        il.Emit(OpCodes.Ret); // Return the result

        return method.CreateDelegate(typeof(Func<T, TOutput>));
    }

	private static readonly IReadOnlyDictionary<Type, Delegate> k_DefaultConversions = new Dictionary<Type, Delegate>
	{
		{
			typeof(string),
			CreateConverter<string>(v => v.ToString())
		},
	};
	protected abstract IReadOnlyDictionary<Type, Delegate> Conversions { get; }
}

public static class BenchmarkTools
{
	public static void MeasurePerformance(string approach, Action action)
	{
		var stringBuilder = new StringBuilder($"Performance Measurement: {approach}\n    ");
		
		// Warm-up
		action();
		
		GC.Collect();
		GC.WaitForPendingFinalizers();
		GC.Collect();
		
		var stopwatch = Stopwatch.StartNew();
		action();
		stopwatch.Stop();
		stringBuilder.Append($"    Execution Time: {stopwatch.ElapsedMilliseconds} ms\n");
		
		var memoryBefore = GC.GetTotalMemory(true);
		action();
		var memoryAfter = GC.GetTotalMemory(false);
		stringBuilder.Append($"    Allocations: {NicifyMemory(memoryAfter - memoryBefore)}\n");
		
		Console.WriteLine(stringBuilder.ToString());
	}

	private static string NicifyMemory(long bytes)
	{
		return bytes switch
		{
			< 1024 => $"{bytes} B",
			< 1024 * 1024 => $"{bytes / 1024} KB",
			_ => $"{bytes / (1024 * 1024)} MB"};
	}
}

public interface IValue
{
	bool As<TTarget>(out TTarget result);
}
;