//****************************************************************************************************** // ValueExpressionParser.cs - Gbtc // // Copyright © 2017, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this // file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 04/09/2017 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text.RegularExpressions; using ExpressionEvaluator; using GSF.Collections; using GSF.Identity; using GSF.Reflection; // ReSharper disable UnusedMember.Local // ReSharper disable StaticMemberInGenericType // ReSharper disable PossibleNullReferenceException namespace GSF.ComponentModel { /// /// Represents a parser for instances. /// public class ValueExpressionParser : ValueExpressionParser { #region [ Constructors ] /// /// Creates a new . /// /// C# expression to be parsed. public ValueExpressionParser(string expression) : base(expression) { } /// /// Creates a new from the specified /// and /// parameters deriving the base expression value from /// . /// /// Source instance. /// Source instance. public ValueExpressionParser(IValueExpressionAttribute valueExpressionAttribute, PropertyInfo property) : base(valueExpressionAttribute, property) { } #endregion #region [ Static ] // Static Fields private static readonly TypeRegistry s_defaultTypeRegistry; private static readonly Regex s_findThisKeywords; // Static Constructor static ValueExpressionParser() { // Setup default type registry for parsing value expression attributes s_defaultTypeRegistry = new TypeRegistry(); s_defaultTypeRegistry.RegisterDefaultTypes(); s_defaultTypeRegistry.RegisterType(); s_defaultTypeRegistry.RegisterType(); s_defaultTypeRegistry.RegisterType("Common", typeof(Common)); s_defaultTypeRegistry.RegisterType("StringExtensions", typeof(StringExtensions)); // Define a regular expression to find "this" keywords s_findThisKeywords = new Regex(@"(^this(?=[^\w]))|((?<=[^\w])this(?=[^\w]))|(^this$)", RegexOptions.Compiled | RegexOptions.Multiline); } // Static Properties /// /// Gets the default instance used for evaluating instances. /// public static TypeRegistry DefaultTypeRegistry => s_defaultTypeRegistry; // Static Methods /// /// Returns a flag that determines if the "this" keyword exists with the specified . /// /// Expression to search. /// true if "this" keyword exists in expression; otherwise, false. public static bool HasThisKeywords(string expression) { lock (s_findThisKeywords) return s_findThisKeywords.IsMatch(expression); } /// /// Replaces references to "this" keyword with a specified . /// /// Expression to search. /// Replacement value for "this" keyword usages. /// An expression with "this" keywords replaced with specified . public static string ReplaceThisKeywords(string expression, string fieldName) { lock (s_findThisKeywords) return s_findThisKeywords.Replace(expression, fieldName); } /// /// Derives an expression based on info with any this /// keywords properly referencing value. /// /// Expression to derive, typically from or . /// Associated instance. /// Associated instance, typically target property for . /// Modeled type name, e.g., typeof<T>.FullName. /// Derived expression with any this keywords properly referencing value. /// /// Specified references the this keyword and must specify a positive . /// /// Parameter cannot be null. public static string DeriveExpression(string expression, IValueExpressionAttribute valueExpressionAttribute, MemberInfo member, string typeName) { if ((object)expression == null) throw new ArgumentNullException(nameof(expression)); // Check for "this" keywords in expression if (HasThisKeywords(expression)) { if (valueExpressionAttribute.EvaluationOrder < 1) throw new EvaluationOrderException($"Value expression attribute for property \"{typeName}.{member.Name}\" references the \"this\" keyword and must specify a positive \"EvaluationOrder\"."); // Replace all references to "this" with "Instance" expression = ReplaceThisKeywords(expression, "Instance"); } return expression; } #endregion } /// /// Represents a typed parser for instances. /// /// Type of associated model. public class ValueExpressionParser : CompiledExpression { #region [ Members ] // Nested Types private class MinimumScope : ValueExpressionScopeBase { } #endregion #region [ Constructors ] /// /// Creates a new . /// /// C# expression to be parsed. public ValueExpressionParser(string expression) : base(expression) { TypeRegistry = ValueExpressionParser.DefaultTypeRegistry; } /// /// Creates a new from the specified /// and /// parameters deriving the base expression value from /// . /// /// Source instance. /// Source instance. public ValueExpressionParser(IValueExpressionAttribute valueExpressionAttribute, PropertyInfo property) : this(DeriveExpression(null, valueExpressionAttribute, property)) { } #endregion #region [ Methods ] /// /// Parses the expression with the provided parameter expression and optional . /// /// Parameter expression used to provide context to parsed instances. /// /// Type registry to use when parsing instances, or null /// to use . /// /// true if parsing an action; otherwise, false for a function. public void Parse(Expression scope, TypeRegistry typeRegistry = null, bool isCall = false) { if ((object)scope == null) throw new ArgumentNullException(nameof(scope)); if ((object)typeRegistry != null) TypeRegistry = typeRegistry; BuildTree(scope, isCall); } #endregion #region [ Static ] // Static Fields // Cached expression value dictionary is defined per type T to reduce contention private static readonly Dictionary s_cachedExpressionValues; private static readonly MethodInfo s_addCachedValueMethod; private static readonly MethodInfo s_getCachedValueMethod; // Static Constructor static ValueExpressionParser() { // Define a table of cached default value expression values s_cachedExpressionValues = new Dictionary(); Type expressionParser = typeof(ValueExpressionParser); s_addCachedValueMethod = expressionParser.GetMethod("AddCachedValue", BindingFlags.Static | BindingFlags.NonPublic); s_getCachedValueMethod = expressionParser.GetMethod("GetCachedValue", BindingFlags.Static | BindingFlags.NonPublic); } // Static Methods /// /// Validates that any static type functionality is initialized. /// /// /// As long as type defines a parameterless constructor, this method /// will create an instance of the modeled class so that any defined static functionality will /// be initialized. Calling this method in advance of any of the static create or update delegate /// generation functions will allow modeled types to self-register any custom symbols and types /// that may be used during evaluation of value expressions attributes, e.g.: /// /// static MyModel() /// { /// TableOperations<MyModel>.TypeRegistry.RegisterType<MyType>(); /// } /// /// /// /// true if type supports a parameterless constructor and was /// successfully initialized; otherwise, false. /// public static bool InitializeType() { // ReSharper disable once ConditionIsAlwaysTrueOrFalse if ((object)typeof(T).GetConstructor(Type.EmptyTypes) != null) return (object)Activator.CreateInstance() != null; return false; } /// /// Generates a delegate that will create new instance of type applying any specified /// or instances that are /// declared on the type properties. /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will create new /// objects of type where properties of the type of have been decorated with /// or attributes. /// The newly created object will automatically have applied any defined default values as specified by /// the encountered attributes. /// /// This function will assign evaluated expression values to properties in a newly created model. /// /// /// /// Generated delegate that will create new instances with default values applied. /// public static Func CreateInstance(IEnumerable properties = null, TypeRegistry typeRegistry = null) { Func createInstanceFunction = CreateInstance(properties, typeRegistry); return () => createInstanceFunction(new MinimumScope()); } /// /// Generates a delegate that will create new instance of type applying any specified /// or instances that are /// declared on the type properties. /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will create new /// objects of type where properties of the type of have been decorated with /// or attributes. /// The newly created object will automatically have applied any defined default values as specified by /// the encountered attributes. /// /// This function will assign evaluated expression values to properties in a newly created model. /// /// /// /// Generated delegate that will create new instances with default values applied. /// /// parameter type. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static Func CreateInstanceForType(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute { Func createInstanceFunction = CreateInstanceForType(properties, typeRegistry); return () => createInstanceFunction(new MinimumScope()); } /// /// Generates a delegate that will update an instance of type applying any specified /// or instances that are /// declared on the type properties. Target instance is /// accepted as the parameter to the returned delegate . /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will update /// objects of type where properties of the type of have been decorated with /// or attributes. /// The updated object will automatically have applied any defined default values as specified by /// the encountered attributes. /// /// This function will assign evaluated expression values to properties in an existing model. /// /// /// /// Generated delegate that will update instances with default values applied. /// public static Action ApplyDefaults(IEnumerable properties = null, TypeRegistry typeRegistry = null) { Action applyDefaultsFunction = ApplyDefaults(properties, typeRegistry); return instance => applyDefaultsFunction(new MinimumScope { Instance = instance }); } /// /// Generates a delegate that will update an instance of type applying any specified /// or instances that are /// declared on the type properties. Target instance is /// accepted as the parameter to the returned delegate . /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will update /// objects of type where properties of the type of have been decorated with /// or attributes. /// The updated object will automatically have applied any defined default values as specified by /// the encountered attributes. /// /// This function will assign evaluated expression values to properties in an existing model. /// /// /// /// Generated delegate that will update instances with default values applied. /// /// parameter type. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static Action ApplyDefaultsForType(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute { Action applyDefaultsFunction = ApplyDefaultsForType(properties, typeRegistry); return instance => applyDefaultsFunction(new MinimumScope { Instance = instance }); } /// /// Generates a delegate that will update an instance of type applying any /// specified instances that are declared on the type /// properties. Target instance is accepted /// as the parameter to the returned delegate . /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will update /// objects of type where properties of the type of have been decorated /// with attributes. The updated object will automatically /// have applied any defined update values as specified by the encountered attributes. /// /// This function will assign evaluated expression values to properties in an existing model. /// /// /// /// Generated delegate that will update instances with update expression values applied. /// public static Action UpdateInstance(IEnumerable properties = null, TypeRegistry typeRegistry = null) { Action updateInstanceFunction = UpdateInstance(properties, typeRegistry); return instance => updateInstanceFunction(new MinimumScope { Instance = instance }); } /// /// Generates a delegate that will update an instance of type applying any /// specified instances that are declared on the type /// properties. Target instance is accepted /// as the parameter to the returned delegate . /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will update /// objects of type where properties of the type of have been decorated /// with attributes. The updated object will automatically /// have applied any defined update values as specified by the encountered attributes. /// /// This function will assign evaluated expression values to properties in an existing model. /// /// /// /// Generated delegate that will update instances with update expression values applied. /// /// parameter type. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static Action UpdateInstanceForType(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute { Action updateInstanceFunction = UpdateInstanceForType(properties, typeRegistry); return instance => updateInstanceFunction(new MinimumScope { Instance = instance }); } /// /// Generates a delegate that will execute expression assignments on an instance of type /// where expressions are instances that are declared /// on the type properties. Target instance is accepted /// as the parameter to the returned delegate . /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, /// or null to use . /// /// /// /// This function is useful for generating a delegate to a compiled function that will execute expression /// assignments on objects of type where properties of the type of have been decorated /// with attributes. Note that the expression in the /// attribute is expected to evaluate to a property /// such that it can be assigned the target type property value. /// /// This function will assign current modeled property values back to expressions, this is often useful /// when a model is being synchronized to an external source, e.g., user interface elements. /// /// /// /// This method is the inverse call for . /// /// /// /// Generated delegate that will execute expression assignments on instances. /// public static Action UpdateExpressions(IEnumerable properties = null, TypeRegistry typeRegistry = null) { Action updateExpressionsFunction = UpdateExpressions(properties, typeRegistry); return instance => updateExpressionsFunction(new MinimumScope { Instance = instance }); } /// /// Generates a delegate that will execute expression assignments on an instance of type /// where expressions are instances that are declared /// on the type properties. Target instance is accepted /// as the parameter to the returned delegate . /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will execute expression /// assignments on objects of type where properties of the type of have been decorated /// with attributes. Note that the expression in the /// attribute is expected to evaluate to a property /// such that it can be assigned the target type property value. /// /// This function will assign current modeled property values back to expressions, this is often useful /// when a model is being synchronized to an external source, e.g., user interface elements. /// /// /// /// Generated delegate that will execute expression assignments on instances. /// /// parameter type. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static Action UpdateExpressionsForType(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute { Action updateExpressionsFunction = UpdateExpressionsForType(properties, typeRegistry); return instance => updateExpressionsFunction(new MinimumScope { Instance = instance }); } /// /// Generates a delegate that will update an instance of type assigning values /// from instances that are declared on the /// type properties to the property values. Target /// instance is accepted as the parameter to the returned delegate . /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or /// null to use . /// /// /// /// This function is useful for generating a delegate to a compiled function that will update objects /// of type where properties of the type of have been decorated with /// attributes. The updated object will automatically /// have applied any defined update values as specified by the encountered attributes. /// /// This function will assign evaluated expression values to properties in an existing model. /// /// /// /// This method is the inverse call for . Internally the method simply calls /// for type . /// /// /// /// Generated delegate that will update instances with update expression values applied. /// public static Action UpdateProperties(IEnumerable properties = null, TypeRegistry typeRegistry = null) { return UpdateInstanceForType(properties, typeRegistry); } /// /// Generates a delegate that will create new instance of type accepting a /// contextual object parameter applying any /// specified or /// instances that are declared on the type properties. /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will create new /// objects of type where properties of the type of have been decorated with /// or attributes. /// The newly created object will automatically have applied any defined default values as specified by /// the encountered attributes. The generated delegate takes a parameter to a contextual object useful /// for providing extra runtime data to attributes; the /// parameter must be derived from . Any public fields, /// methods or properties defined in the derived class will be automatically accessible from the /// expressions declared in the attributes. By default, /// the expressions will have access to the current instance by referencing the /// this keyword, which is an alias to . /// /// This function will assign evaluated expression values to properties in a newly created model. /// /// /// /// Generated delegate that will create new instances with default values applied. /// /// parameter type. public static Func CreateInstance(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TExpressionScope : IValueExpressionScope { return CreateInstanceForType(properties, typeRegistry); } /// /// Generates a delegate that will create new instance of type accepting a /// contextual object parameter applying any /// specified or /// instances that are declared on the type properties. /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will create new /// objects of type where properties of the type of have been decorated with /// or attributes. /// The newly created object will automatically have applied any defined default values as specified by /// the encountered attributes. The generated delegate takes a parameter to a contextual object useful /// for providing extra runtime data to attributes; the /// parameter must be derived from . Any public fields, /// methods or properties defined in the derived class will be automatically accessible from the /// expressions declared in the attributes. By default, /// the expressions will have access to the current instance by referencing the /// this keyword, which is an alias to . /// /// This function will assign evaluated expression values to properties in a newly created model. /// /// /// /// Generated delegate that will create new instances with expression values applied. /// /// parameter type. /// parameter type. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public static Func CreateInstanceForType(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute where TExpressionScope : IValueExpressionScope { ConstructorInfo constructor = typeof(T).GetConstructor(Type.EmptyTypes); if ((object)constructor == null) return _ => throw new InvalidOperationException($"No parameterless constructor exists for type \"{typeof(T).FullName}\"."); if ((object)properties == null) properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(property => property.CanRead && property.CanWrite); List expressions = new List(); ParameterExpression newInstance = Expression.Variable(typeof(T)); ParameterExpression scopeParameter = Expression.Parameter(typeof(TExpressionScope)); TValueExpressionAttribute valueExpressionAttribute; // Sort properties by any specified evaluation order properties = properties.OrderBy(property => property.TryGetAttribute(out valueExpressionAttribute) ? valueExpressionAttribute.EvaluationOrder : 0); // Create new instance and assign to local variable expressions.Add(Expression.Assign(newInstance, Expression.New(constructor))); // Assign new instance to "Instance" property of scope parameter MethodInfo setInstance = typeof(TExpressionScope).GetProperty("Instance").SetMethod; expressions.Add(Expression.Call(scopeParameter, setInstance, newInstance)); // Find any defined value attributes for properties and assign them to new instance foreach (PropertyInfo property in properties) { if (property.TryGetAttribute(out DefaultValueAttribute defaultValueAttribute)) { try { expressions.Add(Expression.Call(newInstance, property.SetMethod, Expression.Constant(defaultValueAttribute.Value, property.PropertyType))); } catch (Exception ex) { return _ => throw new ArgumentException($"Error evaluating \"DefaultValueAttribute\" for property \"{typeof(T).FullName}.{property.Name}\": {ex.Message}", property.Name, ex); } } else if (property.TryGetAttribute(out valueExpressionAttribute)) { string expression = null; try { // Pass along any provided type registry in case attribute needs to pre-parse expression valueExpressionAttribute.TypeRegistry = typeRegistry; expressions.Add(AssignParsedValueExpression(valueExpressionAttribute, typeRegistry, property, scopeParameter, newInstance, out expression)); } catch (EvaluationOrderException ex) { // Need to wrap exceptions in order to keep original call stack return _ => throw new InvalidOperationException(ex.Message, ex); } catch (Exception ex) { return _ => throw new ArgumentException($"Error parsing \"{typeof(TValueExpressionAttribute).Name}\" for property \"{typeof(T).FullName}.{property.Name}\": {ex.Message} for expression \"{expression ?? "undefined"}\"", property.Name, ex); } } } // Return new instance expressions.Add(newInstance); // Return a delegate to compiled function block return Expression.Lambda>(Expression.Block(new[] { newInstance }, expressions), scopeParameter).Compile(); } /// /// Generates a delegate that will update an instance of type accepting /// a contextual object parameter applying any specified /// or /// instances that are declared on the type properties. Target /// instance needs to be assigned to the /// property prior to call. /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will update /// objects of type where properties of the type of have been decorated with /// or attributes. /// The updated object will automatically have applied any defined default values as specified by /// the encountered attributes. The generated delegate takes a parameter to a contextual object useful /// for providing extra runtime data to attributes; the /// parameter must be derived from . Any public fields, /// methods or properties defined in the derived class will be automatically accessible from the /// expressions declared in the attributes. By default, /// the expressions will have access to the current instance by referencing the /// this keyword, which is an alias to . /// /// This function will assign evaluated expression values to properties in an existing model. /// /// /// /// Generated delegate that will update instances with expression values applied. /// /// parameter type. public static Action ApplyDefaults(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TExpressionScope : IValueExpressionScope { return ApplyDefaultsForType(properties, typeRegistry); } /// /// Generates a delegate that will update an instance of type accepting /// a contextual object parameter applying any specified /// or /// instances that are declared on the type properties. Target /// instance needs to be assigned to the /// property prior to call. /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will update /// objects of type where properties of the type of have been decorated with /// or attributes. /// The updated object will automatically have applied any defined default values as specified by /// the encountered attributes. The generated delegate takes a parameter to a contextual object useful /// for providing extra runtime data to attributes; the /// parameter must be derived from . Any public fields, /// methods or properties defined in the derived class will be automatically accessible from the /// expressions declared in the attributes. By default, /// the expressions will have access to the current instance by referencing the /// this keyword, which is an alias to . /// /// This function will assign evaluated expression values to properties in an existing model. /// /// /// /// Generated delegate that will update instances with expression values applied. /// /// parameter type. /// parameter type. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public static Action ApplyDefaultsForType(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute where TExpressionScope : IValueExpressionScope { if ((object)properties == null) properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(property => property.CanRead && property.CanWrite); List expressions = new List(); ParameterExpression instance = Expression.Variable(typeof(T)); ParameterExpression scopeParameter = Expression.Parameter(typeof(TExpressionScope)); TValueExpressionAttribute valueExpressionAttribute; // Sort properties by any specified evaluation order properties = properties.OrderBy(property => property.TryGetAttribute(out valueExpressionAttribute) ? valueExpressionAttribute.EvaluationOrder : 0); // Get "Instance" property of scope parameter and assign to local variable MethodInfo getInstance = typeof(TExpressionScope).GetProperty("Instance").GetMethod; expressions.Add(Expression.Assign(instance, Expression.Call(scopeParameter, getInstance))); // Find any defined value attributes for properties and assign them to instance foreach (PropertyInfo property in properties) { if (property.TryGetAttribute(out DefaultValueAttribute defaultValueAttribute)) { try { expressions.Add(Expression.Call(instance, property.SetMethod, Expression.Constant(defaultValueAttribute.Value, property.PropertyType))); } catch (Exception ex) { return _ => throw new ArgumentException($"Error evaluating \"DefaultValueAttribute\" for property \"{typeof(T).FullName}.{property.Name}\": {ex.Message}", property.Name, ex); } } else if (property.TryGetAttribute(out valueExpressionAttribute)) { string expression = null; try { // Pass along any provided type registry in case attribute needs to pre-parse expression valueExpressionAttribute.TypeRegistry = typeRegistry; expressions.Add(AssignParsedValueExpression(valueExpressionAttribute, typeRegistry, property, scopeParameter, instance, out expression)); } catch (EvaluationOrderException ex) { // Need to wrap exceptions in order to keep original call stack return _ => throw new InvalidOperationException(ex.Message, ex); } catch (Exception ex) { return _ => throw new ArgumentException($"Error parsing \"{typeof(TValueExpressionAttribute).Name}\" for property \"{typeof(T).FullName}.{property.Name}\": {ex.Message} for expression \"{expression ?? "undefined"}\"", property.Name, ex); } } } // Return a delegate to compiled function block return Expression.Lambda>(Expression.Block(new[] { instance }, expressions), scopeParameter).Compile(); } /// /// Generates a delegate that will update an instance of type accepting a /// contextual object parameter applying any specified /// instances that are declared on the type /// properties. Target instance needs to be /// assigned to the property prior to call. /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will update /// objects of type where properties of the type of have been decorated /// with attributes. The updated object will automatically /// have applied any defined update values as specified by the encountered attributes. The generated /// delegate takes a parameter to a contextual object useful for providing extra runtime data to /// attributes; the parameter must be derived from /// . Any public fields, methods or properties defined in the /// derived class will be automatically accessible from the expressions declared in the /// attributes. By default, the expressions will have /// access to the current instance by referencing the this keyword, /// which is an alias to . /// /// This function will assign evaluated expression values to properties in an existing model. /// /// /// /// Generated delegate that will update instances with update expression values applied. /// /// parameter type. public static Action UpdateInstance(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TExpressionScope : IValueExpressionScope { return UpdateInstanceForType(properties, typeRegistry); } /// /// Generates a delegate that will update an instance of type accepting a /// contextual object parameter applying any specified /// instances that are declared on the type /// properties. Target instance needs to be /// assigned to the property prior to call. /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will update /// objects of type where properties of the type of have been decorated /// with attributes. The updated object will automatically /// have applied any defined update values as specified by the encountered attributes. The generated /// delegate takes a parameter to a contextual object useful for providing extra runtime data to /// attributes; the parameter must be derived from /// . Any public fields, methods or properties defined in the /// derived class will be automatically accessible from the expressions declared in the /// attributes. By default, the expressions will have /// access to the current instance by referencing the this keyword, /// which is an alias to . /// /// This function will assign evaluated expression values to properties in an existing model. /// /// /// /// Generated delegate that will update instances with expression values applied. /// /// parameter type. /// parameter type. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public static Action UpdateInstanceForType(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute where TExpressionScope : IValueExpressionScope { if ((object)properties == null) properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(property => property.CanRead && property.CanWrite); List expressions = new List(); ParameterExpression instance = Expression.Variable(typeof(T)); ParameterExpression scopeParameter = Expression.Parameter(typeof(TExpressionScope)); TValueExpressionAttribute valueExpressionAttribute; // Sort properties by any specified evaluation order properties = properties.OrderBy(property => property.TryGetAttribute(out valueExpressionAttribute) ? valueExpressionAttribute.EvaluationOrder : 0); // Get "Instance" property of scope parameter and assign to local variable MethodInfo getInstance = typeof(TExpressionScope).GetProperty("Instance").GetMethod; expressions.Add(Expression.Assign(instance, Expression.Call(scopeParameter, getInstance))); // Find any defined value attributes for properties and assign them to instance foreach (PropertyInfo property in properties) { if (property.TryGetAttribute(out valueExpressionAttribute)) { string expression = null; try { // Pass along any provided type registry in case attribute needs to pre-parse expression valueExpressionAttribute.TypeRegistry = typeRegistry; expressions.Add(AssignParsedValueExpression(valueExpressionAttribute, typeRegistry, property, scopeParameter, instance, out expression)); } catch (EvaluationOrderException ex) { // Need to wrap exceptions in order to keep original call stack return _ => throw new InvalidOperationException(ex.Message, ex); } catch (Exception ex) { return _ => throw new ArgumentException($"Error parsing \"{typeof(TValueExpressionAttribute).Name}\" for property \"{typeof(T).FullName}.{property.Name}\": {ex.Message} for expression \"{expression ?? "undefined"}\"", property.Name, ex); } } } // Return a delegate to compiled function block return Expression.Lambda>(Expression.Block(new[] { instance }, expressions), scopeParameter).Compile(); } /// /// Generates a delegate that will execute expression assignments on an instance of type /// accepting a contextual object parameter where expressions /// are instances that are declared on the type /// properties. Target instance needs to be /// assigned to the property prior to call. /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, /// or null to use . /// /// /// This function is useful for generating a delegate to a compiled function that will execute expression /// assignments on objects of type where properties of the type of have been decorated /// with attributes. The generated delegate takes a parameter /// to a contextual object useful for providing extra runtime data to the expressions defined in attributes /// of type ; the contextual parameter must be derived /// from . Any public fields, methods or properties defined in the /// derived class will be automatically accessible from the expressions declared in the /// attributes. By default, the expressions will have /// access to the current instance by referencing the this keyword, /// which is an alias to . Note that the expression in /// the attribute is expected to evaluate to a property /// such that it can be assigned the target type property value. /// /// This function will assign current modeled property values back to expressions, this is often useful /// when a model is being synchronized to an external source, e.g., user interface elements. /// /// /// /// Generated delegate that will execute expression assignments on instances. /// /// parameter type. public static Action UpdateExpressions(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TExpressionScope : IValueExpressionScope { return UpdateExpressionsForType(properties, typeRegistry); } /// /// Generates a delegate that will execute expression assignments on an instance of type /// accepting a contextual object parameter where expressions /// are instances that are declared on the type /// properties. Target instance needs to be /// assigned to the property prior to call. /// /// Specific properties to target, or null to target all properties. /// /// Type registry to use when parsing instances, or null /// to use . /// /// /// This function is useful for generating a delegate to a compiled function that will execute expression /// assignments on objects of type where properties of the type of have been decorated /// with attributes. The generated delegate takes a parameter /// to a contextual object useful for providing extra runtime data to the expressions defined in attributes /// of type ; the contextual parameter must be derived from /// . Any public fields, methods or properties defined in the /// derived class will be automatically accessible from the expressions declared in the /// attributes. By default, the expressions will have /// access to the current instance by referencing the this keyword, /// which is an alias to . Note that the expression in /// the attribute is expected to evaluate to a property /// such that it can be assigned the target type property value. /// /// This function will assign current modeled property values back to expressions, this is often useful /// when a model is being synchronized to an external source, e.g., user interface elements. /// /// /// /// Generated delegate that will execute expression assignments on instances. /// /// parameter type. /// parameter type. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public static Action UpdateExpressionsForType(IEnumerable properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute where TExpressionScope : IValueExpressionScope { if ((object)properties == null) properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(property => property.CanRead); List expressions = new List(); ParameterExpression scopeParameter = Expression.Parameter(typeof(TExpressionScope)); TValueExpressionAttribute valueExpressionAttribute; // Sort properties by any specified evaluation order properties = properties.OrderBy(property => property.TryGetAttribute(out valueExpressionAttribute) ? valueExpressionAttribute.EvaluationOrder : 0); // Find any defined expression attributes for properties and execute them foreach (PropertyInfo property in properties) { if (property.TryGetAttribute(out valueExpressionAttribute)) { string expression = null; try { // Pass along any provided type registry in case attribute needs to pre-parse expression valueExpressionAttribute.TypeRegistry = typeRegistry; // Derive left assignment operand from raw expression, i.e., not from GetPropertyUpdateValue: string leftOperand = DeriveExpression(valueExpressionAttribute.Expression, valueExpressionAttribute, property); string rightOperand = DeriveExpression(valueExpressionAttribute.GetExpressionUpdateValue(property), valueExpressionAttribute, property); // Parse expression update expression expression = $"{leftOperand} = {rightOperand}"; ValueExpressionParser expressionParser = new ValueExpressionParser(expression); expressionParser.Parse(scopeParameter, typeRegistry, true); if ((object)expressionParser.Expression == null) throw new InvalidOperationException("Failed to compile"); expressions.Add(expressionParser.Expression); } catch (EvaluationOrderException ex) { // Need to wrap exceptions in order to keep original call stack return _ => throw new InvalidOperationException(ex.Message, ex); } catch (Exception ex) { return _ => throw new ArgumentException($"Error parsing \"{typeof(TValueExpressionAttribute).Name}\" for property \"{typeof(T).FullName}.{property.Name}\": {ex.Message} for expression \"{expression ?? "undefined"}\"", property.Name, ex); } } } // Return a delegate to compiled function block if (expressions.Count > 0) return Expression.Lambda>(Expression.Block(expressions), scopeParameter).Compile(); return _ => { }; } private static Expression AssignParsedValueExpression(IValueExpressionAttribute valueExpressionAttribute, TypeRegistry typeRegistry, PropertyInfo property, ParameterExpression scopeParameter, ParameterExpression instance, out string expression) { // Parse value expression expression = DeriveExpression(null, valueExpressionAttribute, property); ValueExpressionParser expressionParser = new ValueExpressionParser(expression); expressionParser.Parse(scopeParameter, typeRegistry); if ((object)expressionParser.Expression == null) throw new InvalidOperationException("Failed to compile"); UnaryExpression getParsedValue = Expression.Convert(expressionParser.Expression, property.PropertyType); if (valueExpressionAttribute.Cached) { ConstantExpression propertyInfo = Expression.Constant(property, typeof(PropertyInfo)); ParameterExpression parsedValue = Expression.Variable(property.PropertyType); ParameterExpression cachedValue = Expression.Variable(typeof(Tuple)); MethodInfo getTupleItem1 = typeof(Tuple).GetProperty("Item1").GetMethod; MethodInfo getTupleItem2 = typeof(Tuple).GetProperty("Item2").GetMethod; BlockExpression addParsedValueToCache = Expression.Block(new[] {parsedValue}, Expression.Assign(parsedValue, getParsedValue), Expression.Call(s_addCachedValueMethod, propertyInfo, Expression.Convert(parsedValue, typeof(object))), Expression.Call(instance, property.SetMethod, parsedValue) ); MethodCallExpression setCachedValue = Expression.Call(instance, property.SetMethod, Expression.Convert(Expression.Call(cachedValue, getTupleItem2), property.PropertyType)); return Expression.Block(new[] {cachedValue}, Expression.Assign(cachedValue, Expression.Call(s_getCachedValueMethod, propertyInfo)), Expression.IfThenElse(Expression.IsTrue(Expression.Call(cachedValue, getTupleItem1)), setCachedValue, addParsedValueToCache) ); } return Expression.Call(instance, property.SetMethod, getParsedValue); } private static string DeriveExpression(string expression, IValueExpressionAttribute valueExpressionAttribute, PropertyInfo property) { return ValueExpressionParser.DeriveExpression(expression ?? valueExpressionAttribute.GetPropertyUpdateValue(property), valueExpressionAttribute, property, typeof(T).FullName); } // Function referenced through reflection - see s_addCachedValueMethod private static void AddCachedValue(PropertyInfo property, object value) { lock (s_cachedExpressionValues) s_cachedExpressionValues.GetOrAdd(property, value); } // Function referenced through reflection - see s_getCachedValueMethod private static Tuple GetCachedValue(PropertyInfo property) { bool exists; object value; lock (s_cachedExpressionValues) exists = s_cachedExpressionValues.TryGetValue(property, out value); return new Tuple(exists, value); } #endregion } }