using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace TestExpressions
public static void Main(string[] args)
var calculations = new Calculations<Data>(typeof(Formulas), "Calc");
var data = new Data { Height = 1, VesselLength = 2, MinWeight = 3, D = 4, E = 5 };
calculations.Calculate(data);
var json = JObject.FromObject(data).ToString();
public class Calculations<TData>
public PropertyInfo DataProperty { get; private set; }
public string PropertyName { get; private set; }
public IEnumerable<string> Dependencies { get; set; }
public MethodInfo ValueMethod { get; set; }
public ParameterInfo[] ValueMethodParameters { get; set; }
public CalculatedProperty(PropertyInfo dataProperty)
this.DataProperty = dataProperty;
this.PropertyName = dataProperty.Name;
private Type DataType { get; set; }
private PropertyInfo[] DataProperties { get; set; }
private IEnumerable<CalculatedProperty> OrderedProperties { get; set; }
private Func<Type, PropertyInfo[], ParameterInfo, string> FormulaParameterToDataPropertyName { get; set; }
public Calculations(Type formulasType)
: this(formulasType, Calculations.PascalCaseFormulaParameterToDataPropertyName, Calculations.SamePropertyNameToValueMethod)
public Calculations(Type formulasType, string prefix)
: this(formulasType, Calculations.PascalCaseFormulaParameterToDataPropertyName, Calculations.GetPrefixedPropertyNameToValueMethod(prefix))
Func<Type, PropertyInfo[], ParameterInfo, string> formulaParameterToDataPropertyName,
Func<Type, MethodInfo[], string, MethodInfo> propertyNameToValueMethod)
this.DataType = typeof(TData);
this.DataProperties = this.DataType.GetProperties();
this.FormulaParameterToDataPropertyName = formulaParameterToDataPropertyName;
var calcProperties = new Dictionary<string, CalculatedProperty>();
var formulasTypeMethods = formulasType.GetMethods();
foreach (var property in this.DataProperties)
var propertyName = property.Name;
var cp = new CalculatedProperty(property);
cp.ValueMethod = propertyNameToValueMethod(formulasType, formulasTypeMethods, propertyName);
cp.Dependencies = new string[0];
if (cp.ValueMethod != null)
cp.ValueMethodParameters = cp.ValueMethod.GetParameters();
cp.Dependencies = cp.ValueMethodParameters.Select(p => this.FormulaParameterToDataPropertyName(this.DataType, this.DataProperties, p)).ToArray();
calcProperties[propertyName] = cp;
var propertyNames = this.DataProperties.Select(p => p.Name);
var orderedNames = TopologicalSort<string>(propertyNames, name => calcProperties[name].Dependencies).ToList();
this.OrderedProperties = calcProperties.Values.OrderBy(p => orderedNames.IndexOf(p.PropertyName)).ToArray();
public void Calculate(TData data)
foreach (var pd in this.OrderedProperties)
if (pd.ValueMethod != null)
var propertyNames = pd.ValueMethodParameters.Select(p => this.FormulaParameterToDataPropertyName(this.DataType, this.DataProperties, p));
var values = propertyNames.Select(a => this.OrderedProperties.First(d => d.PropertyName == a).DataProperty.GetValue(data)).ToArray();
var value = pd.ValueMethod.Invoke(null, values);
pd.DataProperty.SetValue(data, value);
private static IEnumerable<T> TopologicalSort<T>(IEnumerable<T> nodes, Func<T, IEnumerable<T>> connected)
var elems = nodes.ToDictionary(node => node,
node => new HashSet<T>(connected(node)));
var elem = elems.FirstOrDefault(x => x.Value.Count == 0);
throw new ArgumentException("Cyclic connections are not allowed");
foreach (var selem in elems)
selem.Value.Remove(elem.Key);
public static class Calculations
public static string PascalCaseFormulaParameterToDataPropertyName(Type dataType, PropertyInfo[] dataTypeProperties, ParameterInfo parameter)
var name = parameter.Name;
name = name[0].ToString().ToUpperInvariant() + name.Substring(1);
public static MethodInfo SamePropertyNameToValueMethod(Type formulasType, MethodInfo[] formulasTypeMethods, string propertyName)
var methodName = propertyName;
var method = formulasType.GetMethod(methodName);
public static Func<Type, MethodInfo[], string, MethodInfo> GetPrefixedPropertyNameToValueMethod(string prefix)
return (formulasType, formulasTypeMethods, propertyName) => GetPropertyNameToValueMethodWithPrefix(prefix, formulasType, formulasTypeMethods, propertyName);
private static MethodInfo GetPropertyNameToValueMethodWithPrefix(string prefix, Type formulasType, MethodInfo[] formulasTypeMethods, string propertyName)
var methodName = prefix + propertyName;
var method = formulasType.GetMethod(methodName);
public static class Formulas
public static double CalcMinWeight(double height, double e)
public static double CalcD(double vesselLength, double minWeight)
return vesselLength * minWeight;
public static double CalcE(double height, double vesselLength)
return height - vesselLength;
public double Height { get; set; }
public double VesselLength { get; set; }
public double MinWeight { get; set; }
public double D { get; set; }
public double E { get; set; }