using InABox.Clients; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace InABox.Core { public static class Expressions { #region Property Getter Setter Functions // Allows setting of object properties via cached expressions public static Expression NullPropagatingPropertyOrField(Expression expression, string propName) { var param = Expression.Parameter(expression.Type); var access = Expression.PropertyOrField(param, propName); return Expression.Block(new[] { param }, Expression.Assign(param, expression), Expression.Condition(Expression.Equal(param, Expression.Constant(null)), Expression.Default(access.Type), access)); } private static Func? MakeGetter(Type objectType, string propname, bool propagateNulls = false) { try { var objectParameter = Expression.Parameter(typeof(T), "o"); Expression param; if(typeof(T) != objectType) { param = Expression.ConvertChecked(objectParameter, objectType); } else { param = objectParameter; } var body = param; if(propname != "") { foreach (var member in propname.Split('.')) body = propagateNulls ? NullPropagatingPropertyOrField(body, member) : Expression.PropertyOrField(body, member); } if(typeof(TProp) != body.Type) { body = Expression.Convert(body, typeof(TProp)); } var lambda = Expression.Lambda>(body, objectParameter); return lambda.Compile(); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); return null; } } public static Func Getter(Expression> expression, bool propagateNulls = false) { return propagateNulls ? MakeGetter(typeof(T), CoreUtils.GetFullPropertyName(expression, "."), propagateNulls: true) : expression.Compile(); } public static Func Getter(string propname, bool propagateNulls = false) { return MakeGetter(typeof(T), propname, propagateNulls: propagateNulls); } public static Func Getter(Type objectType, string propname, bool propagateNulls = false) { return MakeGetter(objectType, propname, propagateNulls: propagateNulls); } public static Func Getter(string propname, string key) { Func result = null; try { var param = Expression.Parameter(typeof(T), "o"); Expression body = param; foreach (var member in propname.Split('.')) body = Expression.PropertyOrField(body, member); Expression keyExpr = Expression.Constant(key, typeof(string)); //Parameter(typeof(string)); // Alternative, note that we could even look for the type of parameters, if there are indexer overloads. var indexer = (from p in body.Type.GetDefaultMembers().OfType() // This check is probably useless. You can't overload on return value in C#. where p.PropertyType == typeof(object) let q = p.GetIndexParameters() // Here we can search for the exact overload. Length is the number of "parameters" of the indexer, and then we can check for their type. where q.Length == 1 && q[0].ParameterType == typeof(string) select p).Single(); var indexExpr = Expression.Property(body, indexer, keyExpr); var lambdaGetter = Expression.Lambda>(indexExpr, param); result = lambdaGetter.Compile(); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } return result; } public static Func Getter(Type objectType, string propname, string key) { Func result = null; try { var objectParameter = Expression.Parameter(typeof(object), "o"); var param = Expression.ConvertChecked(objectParameter, objectType); Expression body = param; foreach (var member in propname.Split('.')) body = Expression.PropertyOrField(body, member); Expression keyExpr = Expression.Constant(key, typeof(string)); //Parameter(typeof(string)); // Alternative, note that we could even look for the type of parameters, if there are indexer overloads. var indexer = (from p in body.Type.GetDefaultMembers().OfType() // This check is probably useless. You can't overload on return value in C#. where p.PropertyType == typeof(object) let q = p.GetIndexParameters() // Here we can search for the exact overload. Length is the number of "parameters" of the indexer, and then we can check for their type. where q.Length == 1 && q[0].ParameterType == typeof(string) select p).Single(); var indexExpr = Expression.Property(body, indexer, keyExpr); var lambdaGetter = Expression.Lambda>(indexExpr, objectParameter); result = lambdaGetter.Compile(); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } return result; } public static bool IsWriteable(Expression expression) { switch (expression.NodeType) { case ExpressionType.Index: return expression is IndexExpression index && index.Indexer is PropertyInfo indexer && indexer.CanWrite; case ExpressionType.MemberAccess: if(expression is MemberExpression me) { if (me.Member is PropertyInfo prop) return prop.CanWrite; else if (me.Member is FieldInfo field) return !(field.IsInitOnly || field.IsLiteral); } return false; case ExpressionType.Parameter: return true; } return false; } public static Action Setter(string propname) { var param = Expression.Parameter(typeof(T), "o"); Expression body = param; foreach (var member in propname.Split('.')) body = Expression.PropertyOrField(body, member); var valueParameter = Expression.Parameter(typeof(object), "v"); var value = Expression.ConvertChecked(valueParameter, body.Type); Expression expression; if (IsWriteable(body)) { try { expression = Expression.Assign(body, value); } catch { expression = Expression.Empty(); } } else { expression = Expression.Empty(); } var lambda = Expression.Lambda>(expression, param, valueParameter); var compiled = lambda.Compile(); return compiled; } public static Action Setter(Type objectType, string propname, Type? type = null) { Action? compiled = null; try { var objectParameter = Expression.Parameter(typeof(object), "o"); var param = Expression.ConvertChecked(objectParameter, objectType); Expression body = param; foreach (var member in propname.Split('.')) body = Expression.PropertyOrField(body, member); var valueParameter = Expression.Parameter(typeof(object), "v"); UnaryExpression value; if (type != null) { var val1 = Expression.Convert(valueParameter, type); value = Expression.ConvertChecked(val1, body.Type); } else { value = Expression.ConvertChecked(valueParameter, body.Type); } Expression expression; if (IsWriteable(body)) { try { expression = Expression.Assign(body, value); } catch { expression = Expression.Empty(); } } else { expression = Expression.Empty(); } var lambda = Expression.Lambda>(expression, objectParameter, valueParameter); compiled = lambda.Compile(); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } return compiled; } public static Action Setter(string propname, string key) { Action result = null; try { var param = Expression.Parameter(typeof(T), "o"); Expression body = param; foreach (var member in propname.Split('.')) body = Expression.PropertyOrField(body, member); Expression keyExpr = Expression.Constant(key, typeof(string)); var valueExpr = Expression.Parameter(typeof(object)); // Alternative, note that we could even look for the type of parameters, if there are indexer overloads. var indexer = (from p in body.Type.GetDefaultMembers().OfType() // This check is probably useless. You can't overload on return value in C#. where p.PropertyType == typeof(object) let q = p.GetIndexParameters() // Here we can search for the exact overload. Length is the number of "parameters" of the indexer, and then we can check for their type. where q.Length == 1 && q[0].ParameterType == typeof(string) select p).Single(); var indexExpr = Expression.Property(body, indexer, keyExpr); var assign = Expression.Assign(indexExpr, valueExpr); var lambdaSetter = Expression.Lambda>(assign, param, valueExpr); result = lambdaSetter.Compile(); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } return result; } public static Action Setter(Type objectType, string propname, string key) { Action result = null; try { var objectParameter = Expression.Parameter(typeof(object), "o"); var param = Expression.ConvertChecked(objectParameter, objectType); Expression body = param; foreach (var member in propname.Split('.')) body = Expression.PropertyOrField(body, member); Expression keyExpr = Expression.Constant(key, typeof(string)); var valueExpr = Expression.Parameter(typeof(object)); // Alternative, note that we could even look for the type of parameters, if there are indexer overloads. var indexer = (from p in body.Type.GetDefaultMembers().OfType() // This check is probably useless. You can't overload on return value in C#. where p.PropertyType == typeof(object) let q = p.GetIndexParameters() // Here we can search for the exact overload. Length is the number of "parameters" of the indexer, and then we can check for their type. where q.Length == 1 && q[0].ParameterType == typeof(string) select p).Single(); var indexExpr = Expression.Property(body, indexer, keyExpr); var assign = Expression.Assign(indexExpr, valueExpr); var lambdaSetter = Expression.Lambda>(assign, objectParameter, valueExpr); result = lambdaSetter.Compile(); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } return result; } #endregion } }