/// <summary> /// Contructs immutable object from existing one with changed property specified by lambda expression. /// </summary> /// <typeparam name="TInstance">Immutable object type.</typeparam> /// <typeparam name="TValue">Value to set type.</typeparam> /// <param name="source">Original immutable object.</param> /// <param name="expression">Navigation property specifying what to change.</param> /// <param name="value">Value to set in the resulting object.</param> /// <returns>Immutable object with changed property.</returns> public TInstance With <TInstance, TValue>(TInstance source, Expression <Func <TInstance, TValue> > expression, TValue value) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (expression == null) { throw new ArgumentNullException(nameof(expression)); } var processContext = new ProcessContext <TInstance>() { Source = source, Target = value as object, SourceParameterExpression = expression.Parameters.Single(), InstanceExpression = expression.Body }; var actualValue = (TValue)ResolveInstance(processContext); if (object.Equals(actualValue, value)) { return(source); } while (processContext.InstanceExpression != processContext.SourceParameterExpression) { if (TryProcessMemberExpression(processContext)) { continue; } throw new NotSupportedException($"Unable to process expression. Expression: '{processContext.InstanceExpression}'."); } var target = (TInstance)processContext.Target; processContext.AffectedProperties.Reverse(); OnEmit?.Invoke(source, target, value, processContext.AffectedProperties.ToArray()); return(target); }
private object ResolveInstance <TSource>(ProcessContext <TSource> processContext) { var key = new InstanceExpressionCacheKey(typeof(TSource), processContext.InstanceExpression); var compiledExpression = default(ResolveInstanceDelegate <TSource>); if (ResolveInstanceExpressionCache.TryGetValue(key, out var resolveInstanceDelegate)) { compiledExpression = (ResolveInstanceDelegate <TSource>)resolveInstanceDelegate; } else { var instanceConvertExpression = Expression.Convert(processContext.InstanceExpression, typeof(object)); var lambdaExpression = Expression.Lambda <ResolveInstanceDelegate <TSource> >(instanceConvertExpression, processContext.SourceParameterExpression); compiledExpression = lambdaExpression.Compile(); ResolveInstanceExpressionCache[key] = compiledExpression; } return(compiledExpression.Invoke(processContext.Source)); }
private bool TryProcessMemberExpression <TSource>(ProcessContext <TSource> processContext) { if (processContext.InstanceExpression is MemberExpression memberExpression && memberExpression.Member is PropertyInfo property) { processContext.InstanceExpression = memberExpression.Expression; var result = processContext.Target; var type = memberExpression.Expression.Type; var instance = ResolveInstance(processContext); var activationContext = GetActivationContext(type, type); var arguments = ResolveActivatorArguments(activationContext.ParameterResolvers, property, instance, ref result); processContext.Target = activationContext.Activator.Invoke(arguments); processContext.AffectedProperties.Add(property.Name); return(true); } return(false); }