Innovenergy_trunk/csharp/Lib/Utils/Operators.cs

95 lines
3.0 KiB
C#
Raw Normal View History

2023-03-01 07:04:53 +00:00
using System.Reflection;
using static System.Reflection.BindingFlags;
namespace InnovEnergy.Lib.Utils;
public static class Operators
{
public static Func<T, T, T> CreateBinaryOpForProps<T>(this String op)
{
var methodName = GetMethodNameForBinaryOp(op);
var props = typeof(T)
.GetProperties(Instance | Public)
.Where(p => p.CanWrite)
.Select(p =>
(
prop: p,
op: GetOpMethod(methodName, p.PropertyType) ??
throw new ArgumentException($"Type {p.PropertyType.Name} " +
$"of property {p.Name} " +
$"has no suitable {op} operator defined."))
)
.ToArray();
var ctr = typeof(T).GetConstructors().FirstOrDefault(c => c.GetParameters().Length == 0);
if (ctr is null)
throw new ArgumentException($"Type {typeof(T).Name} has no suitable parameterless constructor.");
T Op(T left, T right)
{
// TODO: make this faster/nicer using Expression API (low prio)
var result = ctr.Invoke(null);
foreach (var (p, m) in props)
{
var l = p.GetValue(left);
var r = p.GetValue(right);
var s = m.Invoke(null, new[] { l, r });
p.SetValue(result, s);
}
return (T) result;
}
return Op;
}
private static String GetMethodNameForBinaryOp(String op)
{
// from https://stackoverflow.com/a/29495075
return op switch
{
"&" => "op_BitwiseAnd",
"|" => "op_BitwiseOr",
"+" => "op_Addition",
"-" => "op_Subtraction",
"/" => "op_Division",
"%" => "op_Modulus",
"*" => "op_Multiply",
"<<" => "op_LeftShift",
">>" => "op_RightShift",
"^" => "op_ExclusiveOr",
"==" => "op_Equality",
"!=" => "op_Inequality",
">" => "op_GreaterThan",
"<" => "op_LessThan",
">=" => "op_GreaterThanOrEqual",
"<=" => "op_LessThanOrEqual",
_ => throw new ArgumentException("unknown operator", nameof(op))
};
}
private static MethodInfo? GetOpMethod(String? methodName, Type type)
{
return type
.GetMethods(Static | Public)
.FirstOrDefault(m => m.Name == methodName
&& m.ReturnType == type
&& m.IsMonoidOp());
}
private static Boolean IsMonoidOp(this MethodInfo m)
{
var ps = m.GetParameters();
return ps.Length == 2 &&
ps.All(p => p.ParameterType == m.ReturnType);
}
}