using System; 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 Func Getter(Expression> expression) { return expression.Compile(); } public static Func Getter(string propname) { Func? result = null; try { var param = Expression.Parameter(typeof(T), "x"); Expression body = param; foreach (var member in propname.Split('.')) body = Expression.PropertyOrField(body, member); var lambda = Expression.Lambda>(body, param); result = lambda.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) { 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 conversion = Expression.Convert(body, typeof(object)); var lambda = Expression.Lambda>(conversion, objectParameter); result = lambda.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(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 } }