using System.Reflection; using static System.Reflection.BindingFlags; namespace InnovEnergy.Lib.Utils; public static class Operators { public static Func CreateBinaryOpForProps(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); } }