public RecursiveObjectCopier(Expression <Func <S, D> > specifications = null)
        {
            //if (specifications == null) throw new ArgumentException(nameof(specifications));
            changes = new ObjectChangesRegister(null, true);
            originalSpecifications = specifications;
            specifications         = ProjectionExpression <S> .BuildExpression(specifications, null, changes);

            FullExpression = specifications;
            transform      = specifications.Compile();
            bool hasIEnumerables;

            paths           = changes.ComputePaths(out hasIEnumerables);
            HasIEnumerables = hasIEnumerables;
            changes.IndexProperties();
        }
        internal static LambdaExpression BuildInternalExpression(LambdaExpression exp, Type filter = null, ObjectChangesRegister changesRegister = null)
        {
            ParameterExpression parameterExpression = exp.Parameters.First();

            if (exp.Body.NodeType == ExpressionType.MemberInit)
            {
                var bindings = (exp.Body as MemberInitExpression).Bindings;
                if (bindings == null || bindings.Count == 0)
                {
                    var res = Expression.Lambda(
                        createMemberInit(parameterExpression, exp.ReturnType, parameterExpression.Type, filter, changesRegister),
                        parameterExpression);
                    var key = GetCacheKey(exp.ReturnType, parameterExpression.Type, filter);
                    try
                    {
                        ExpressionCache.TryAdd(key, res);
                    }
                    catch { }
                    return(res);
                }
            }
            var pres = processTreeRec(exp.Body, parameterExpression, filter, null, null, null, changesRegister);

            return(Expression.Lambda(pres, parameterExpression));
        }
        public static Expression <Func <TSource, TDest> > BuildExpression <TDest>(Expression <Func <TSource, TDest> > custom, Type filter = null, ObjectChangesRegister changesRegister = null)
        {
            ParameterExpression parameterExpression = custom == null?Expression.Parameter(typeof(TSource), "src") : custom.Parameters.First();

            Expression pres;

            if (custom == null)
            {
                pres = createMemberInit <TDest>(parameterExpression, filter, changesRegister);
            }
            else
            {
                pres = processTreeRec(custom.Body, parameterExpression, filter, null, null, null, changesRegister);
            }

            var expression = Expression.Lambda <Func <TSource, TDest> >(pres, parameterExpression);

            if (custom == null)
            {
                var key = GetCacheKey <TDest>();
                try
                {
                    ExpressionCache.TryAdd(key, expression);
                }
                catch { }
            }

            return(expression);
        }
        private static Expression processTreeRec(
            Expression node,
            ParameterExpression parameterExpression,
            Type filter   = null,
            string prefix = null,
            Stack <PropertyInfo> sourceProperties = null,
            PropertyInfo currProperty             = null,
            ObjectChangesRegister changesRegister = null)
        {
            if (node.NodeType == ExpressionType.MemberInit)
            {
                if (changesRegister != null)
                {
                    changesRegister.MoveToComplex();
                }
                var currType = sourceProperties != null &&
                               sourceProperties.Count > 0 ? sourceProperties.Peek().PropertyType : parameterExpression.Type;
                string newPrefix      = prefix == null ? currProperty?.Name : prefix + currProperty?.Name;;
                var    sourceProperty = newPrefix == null ? null : currType.GetProperty(newPrefix);
                if (currProperty != null && currProperty.PropertyType.GetTypeInfo().IsInterface)
                {
                    filter = currProperty.PropertyType;
                }
                else
                {
                    filter = null;
                }
                if (sourceProperty != null)
                {
                    if (sourceProperties == null)
                    {
                        sourceProperties = new Stack <PropertyInfo>();
                    }
                    sourceProperties.Push(sourceProperty);
                    var res = completeMemberInit(node as MemberInitExpression, parameterExpression, filter, null, sourceProperties, changesRegister);
                    sourceProperties.Pop();
                    if (sourceProperties.Count == 0)
                    {
                        sourceProperties = null;
                    }
                    return(res);
                }
                else
                {
                    return(completeMemberInit(node as MemberInitExpression, parameterExpression, filter, newPrefix, sourceProperties, changesRegister));
                }
            }

            else if (node.NodeType == ExpressionType.Conditional)
            {
                var cond    = node as ConditionalExpression;
                var ifTrue  = processTreeRec(cond.IfTrue, parameterExpression, filter, prefix, sourceProperties, currProperty, changesRegister);
                var ifFalse = processTreeRec(cond.IfFalse, parameterExpression, filter, prefix, sourceProperties, currProperty, changesRegister);
                if (ifTrue == cond.IfTrue && ifFalse == cond.IfFalse)
                {
                    return(node);
                }
                return(Expression.Condition(cond.Test,
                                            ifTrue,
                                            ifFalse));
            }
            else if (node.NodeType == ExpressionType.Convert)
            {
                var conv      = node as UnaryExpression;
                var toConvert = processTreeRec(conv.Operand, parameterExpression, filter, prefix, sourceProperties, currProperty, changesRegister);
                if (toConvert == conv.Operand)
                {
                    return(node);
                }
                return(Expression.Convert(toConvert,
                                          conv.Type, conv.Method));
            }
            else if (node.NodeType == ExpressionType.ConvertChecked)
            {
                var conv      = node as UnaryExpression;
                var toConvert = processTreeRec(conv.Operand, parameterExpression, filter, prefix, sourceProperties, currProperty, changesRegister);
                if (toConvert == conv.Operand)
                {
                    return(node);
                }
                return(Expression.ConvertChecked(toConvert,
                                                 conv.Type, conv.Method));
            }
            else if (node.NodeType == ExpressionType.New)
            {
                if (changesRegister != null)
                {
                    changesRegister.MoveToComplex();
                }
                return(node);
            }
            else
            {
                return(node);
            }
        }
        private static MemberInitExpression createMemberInit(ParameterExpression parameterExpression, Type destination, Type source = null, Type filter = null, ObjectChangesRegister changesRegister = null)
        {
            var internalBindings = BuildBindings(destination, source, filter);
            IEnumerable <MemberAssignment> bindings;

            if (changesRegister != null)
            {
                changesRegister.MoveToComplex();
                var lbindings = new List <MemberAssignment>();
                if (internalBindings != null)
                {
                    foreach (var binding in internalBindings)
                    {
                        var bind = BuildBinding(parameterExpression, binding, null, true);
                        lbindings.Add(bind);
                        changesRegister
                        .AddChange(new ObjectChangesRegister(binding.Destination as PropertyInfo, false, null, bind.Expression));
                    }
                }
                bindings = lbindings;
            }
            else
            {
                bindings = internalBindings
                           .Select(m => BuildBinding(parameterExpression, m, null, changesRegister != null));
            }

            return(Expression.MemberInit(Expression.New(destination), bindings));
        }
        private static MemberInitExpression completeMemberInit(
            MemberInitExpression node,
            ParameterExpression parameterExpression,
            Type filter   = null,
            string prefix = null,
            Stack <PropertyInfo> sourceProperties = null,
            ObjectChangesRegister changesRegister = null)
        {
            var customAssignements  = node.Bindings.Where(m => m.BindingType == MemberBindingType.Assignment).Select(m => m as MemberAssignment).ToList();
            var assignedProperties  = customAssignements.Select(m => m.Member.Name).ToList();
            var internalProjections = customAssignements.Where(m => getNestedSelect(m.Expression) != null);
            var otherAssignements   = customAssignements.Except(internalProjections);
            List <MemberAssignment> modifiedAssignements    = null;
            List <MemberAssignment> modifiedAssignementsOld = null;

            if (internalProjections != null)
            {
                foreach (var projection in internalProjections)
                {
                    var select             = getNestedSelect(projection.Expression);
                    var exp                = select.Arguments[1] as LambdaExpression;
                    var originalCollection = select.Arguments[0] as MemberExpression;
                    if (exp != null)
                    {
                        Type     childFilter  = null;
                        var      propertyInfo = (projection.Member as PropertyInfo);
                        TypeInfo childType    = propertyInfo.PropertyType.GetTypeInfo();
                        if (childType.IsGenericType && childType.GenericTypeArguments.Length == 1 && childType.GenericTypeArguments[0].GetTypeInfo().IsInterface)
                        {
                            childFilter = childType.GenericTypeArguments[0];
                        }
                        var childChangesRegister = changesRegister == null ? null:  new ObjectChangesRegister(propertyInfo, true, childType.GenericTypeArguments[0], null, originalCollection.Member as PropertyInfo);
                        var newExpression        = copyCallChain(projection.Expression as MethodCallExpression, select,
                                                                 Expression.TypeAs(
                                                                     Expression.Call(select.Method, originalCollection, BuildInternalExpression(exp, childFilter, childChangesRegister)),
                                                                     select.Method.ReturnType)
                                                                 );
                        var newAssignement = changesRegister == null?Expression.Bind(projection.Member,
                                                                                     newExpression)
                                                 :
                                                 Expression.Bind(projection.Member,
                                                                 Expression.Condition(Expression.Equal(copyMemberAccesses(select.Arguments[0]), Expression.Constant(null)),
                                                                                      Expression.Constant(null, newExpression.Type),
                                                                                      newExpression));

                        if (changesRegister != null)
                        {
                            childChangesRegister.SetExpression(newExpression);
                            changesRegister.AddChange(childChangesRegister);
                        }

                        if (modifiedAssignements == null)
                        {
                            modifiedAssignements    = new List <MemberAssignment>();
                            modifiedAssignementsOld = new List <MemberAssignment>();
                        }
                        modifiedAssignements.Add(newAssignement);
                        modifiedAssignementsOld.Add(projection);
                    }
                }
            }
            if (otherAssignements != null)
            {
                foreach (var assignement in otherAssignements)
                {
                    var newChange = changesRegister == null ? null : new ObjectChangesRegister(assignement.Member as PropertyInfo, false, null, null);
                    var newNode   = processTreeRec(assignement.Expression, parameterExpression, filter, prefix, sourceProperties, assignement.Member as PropertyInfo, newChange);
                    if (changesRegister != null)
                    {
                        newChange.SetExpression(newNode);
                        changesRegister.AddChange(newChange);
                    }
                    if (newNode != assignement.Expression)
                    {
                        var newAssignement = Expression.Bind(assignement.Member, newNode);
                        if (modifiedAssignements == null)
                        {
                            modifiedAssignements    = new List <MemberAssignment>();
                            modifiedAssignementsOld = new List <MemberAssignment>();
                        }
                        modifiedAssignements.Add(newAssignement);
                        modifiedAssignementsOld.Add(assignement);
                    }
                }
            }
            var innerBindings = BuildBindings(node.NewExpression.Type,
                                              sourceProperties != null && sourceProperties.Count > 0 ?
                                              sourceProperties.Peek().PropertyType :
                                              parameterExpression.Type,
                                              filter, prefix)
                                .Where(m => !assignedProperties.Contains(m.Destination.Name));
            IEnumerable <MemberAssignment> bindings;

            if (innerBindings != null && changesRegister != null)
            {
                var lbindings = new List <MemberAssignment>();
                foreach (var binding in innerBindings)
                {
                    var bind = BuildBinding(parameterExpression, binding, sourceProperties, true);
                    lbindings.Add(bind);
                    changesRegister
                    .AddChange(new ObjectChangesRegister(binding.Destination as PropertyInfo, false, null, bind.Expression));
                }
                bindings = lbindings.Union(customAssignements);
            }
            else
            {
                bindings = innerBindings
                           .Select(m => BuildBinding(parameterExpression, m, sourceProperties, changesRegister != null))
                           .Union(customAssignements);
            }
            if (modifiedAssignements != null && modifiedAssignements.Count > 0)
            {
                bindings = bindings.Except(modifiedAssignementsOld)
                           .Union(modifiedAssignements);
            }
            return(Expression.MemberInit(node.NewExpression, bindings));
        }