CSharp · Console
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);
}