tools/AutoMapper/Mappers/ExpressionMapper.cs (237 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper.Configuration;
using AutoMapper.XpressionMapper.Extensions;
using static System.Linq.Expressions.Expression;
namespace AutoMapper.Mappers
{
public class ExpressionMapper : IObjectMapper
{
private static TDestination Map<TSource, TDestination>(TSource expression, ResolutionContext context)
where TSource : LambdaExpression
where TDestination : LambdaExpression => context.Mapper.MapExpression<TDestination>(expression);
private static readonly MethodInfo MapMethodInfo = typeof(ExpressionMapper).GetDeclaredMethod(nameof(Map));
public bool IsMatch(TypePair context) => typeof(LambdaExpression).IsAssignableFrom(context.SourceType)
&& context.SourceType != typeof(LambdaExpression)
&& typeof(LambdaExpression).IsAssignableFrom(context.DestinationType)
&& context.DestinationType != typeof(LambdaExpression);
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression) =>
Call(null,
MapMethodInfo.MakeGenericMethod(sourceExpression.Type, destExpression.Type),
sourceExpression,
contextExpression);
internal class MappingVisitor : ExpressionVisitor
{
private IList<Type> _destSubTypes = new Type[0];
private readonly IConfigurationProvider _configurationProvider;
private readonly TypeMap _typeMap;
private readonly Expression _oldParam;
private readonly Expression _newParam;
private readonly MappingVisitor _parentMappingVisitor;
public MappingVisitor(IConfigurationProvider configurationProvider, IList<Type> destSubTypes)
: this(configurationProvider, null, Parameter(typeof(Nullable)), Parameter(typeof(Nullable)), null, destSubTypes)
{
}
internal MappingVisitor(IConfigurationProvider configurationProvider, TypeMap typeMap, Expression oldParam, Expression newParam, MappingVisitor parentMappingVisitor = null, IList<Type> destSubTypes = null)
{
_configurationProvider = configurationProvider;
_typeMap = typeMap;
_oldParam = oldParam;
_newParam = newParam;
_parentMappingVisitor = parentMappingVisitor;
if (destSubTypes != null)
_destSubTypes = destSubTypes;
}
protected override Expression VisitConstant(ConstantExpression node) => ReferenceEquals(node, _oldParam) ? _newParam : node;
protected override Expression VisitParameter(ParameterExpression node) => ReferenceEquals(node, _oldParam) ? _newParam : node;
protected override Expression VisitMethodCall(MethodCallExpression node) => base.VisitMethodCall(GetConvertedMethodCall(node));
protected override Expression VisitExtension(Expression node) => (int)node.NodeType == 10000 ? node : base.VisitExtension(node);
private MethodCallExpression GetConvertedMethodCall(MethodCallExpression node)
{
if (!node.Method.IsGenericMethod)
return node;
var convertedArguments = Visit(node.Arguments);
var convertedMethodArgumentTypes = node.Method.GetGenericArguments().Select(t => GetConvertingTypeIfExists(node.Arguments, t, convertedArguments)).ToArray();
var convertedMethodCall = node.Method.GetGenericMethodDefinition().MakeGenericMethod(convertedMethodArgumentTypes);
return Call(convertedMethodCall, convertedArguments);
}
private static Type GetConvertingTypeIfExists(IList<Expression> args, Type t, IList<Expression> arguments)
{
var matchingArgument = args.Where(a => !a.Type.IsGenericType()).FirstOrDefault(a => a.Type == t);
if (matchingArgument != null)
{
var index = args.IndexOf(matchingArgument);
return index < 0 ? t : arguments[index].Type;
}
var matchingEnumerableArgument = args.Where(a => a.Type.IsGenericType()).FirstOrDefault(a => a.Type.GetTypeInfo().GenericTypeArguments[0] == t);
var index2 = args.IndexOf(matchingEnumerableArgument);
return index2 < 0 ? t : arguments[index2].Type.GetTypeInfo().GenericTypeArguments[0];
}
protected override Expression VisitBinary(BinaryExpression node)
{
var newLeft = Visit(node.Left);
var newRight = Visit(node.Right);
if (newLeft.Type != newRight.Type && newRight.Type == typeof(string))
newLeft = Call(newLeft, typeof(object).GetDeclaredMethod("ToString"));
if (newRight.Type != newLeft.Type && newLeft.Type == typeof(string))
newRight = Call(newRight, typeof(object).GetDeclaredMethod("ToString"));
CheckNullableToNonNullableChanges(node.Left, node.Right, ref newLeft, ref newRight);
CheckNullableToNonNullableChanges(node.Right, node.Left, ref newRight, ref newLeft);
return MakeBinary(node.NodeType, newLeft, newRight);
}
private static void CheckNullableToNonNullableChanges(Expression left, Expression right, ref Expression newLeft, ref Expression newRight)
{
if (GoingFromNonNullableToNullable(left, newLeft))
if (BothAreNonNullable(right, newRight))
UpdateToNullableExpression(right, out newRight);
else if (BothAreNullable(right, newRight))
UpdateToNonNullableExpression(right, out newRight);
if (GoingFromNonNullableToNullable(newLeft, left))
if (BothAreNonNullable(right, newRight))
UpdateToNullableExpression(right, out newRight);
else if (BothAreNullable(right, newRight))
UpdateToNonNullableExpression(right, out newRight);
}
private static void UpdateToNullableExpression(Expression right, out Expression newRight)
{
newRight = right is ConstantExpression expression
? Constant(expression.Value,
typeof(Nullable<>).MakeGenericType(right.Type))
: throw new AutoMapperMappingException(
"Mapping a BinaryExpression where one side is nullable and the other isn't");
}
private static void UpdateToNonNullableExpression(Expression right, out Expression newRight)
{
if (right is ConstantExpression expression)
{
var t = right.Type.IsNullableType()
? right.Type.GetGenericArguments()[0]
: right.Type;
newRight = Constant(expression.Value, t);
}
else if (right is UnaryExpression)
newRight = ((UnaryExpression) right).Operand;
else
throw new AutoMapperMappingException(
"Mapping a BinaryExpression where one side is nullable and the other isn't");
}
private static bool GoingFromNonNullableToNullable(Expression node, Expression newLeft)
=> !node.Type.IsNullableType() && newLeft.Type.IsNullableType();
private static bool BothAreNullable(Expression node, Expression newLeft)
=> node.Type.IsNullableType() && newLeft.Type.IsNullableType();
private static bool BothAreNonNullable(Expression node, Expression newLeft)
=> !node.Type.IsNullableType() && !newLeft.Type.IsNullableType();
protected override Expression VisitLambda<T>(Expression<T> expression)
{
return expression.Parameters.Any(b => b.Type == _oldParam.Type)
? VisitLambdaExpression(expression)
: VisitAllParametersExpression(expression);
}
private Expression VisitLambdaExpression<T>(Expression<T> expression)
{
var convertedBody = Visit(expression.Body);
var convertedArguments = expression.Parameters.Select(e => Visit(e) as ParameterExpression).ToList();
return Lambda(convertedBody, convertedArguments);
}
private Expression VisitAllParametersExpression<T>(Expression<T> expression)
{
var visitors = (
from t in expression.Parameters
let sourceParamType = t.Type
from destParamType in _destSubTypes.Where(dt => dt != sourceParamType)
let a = destParamType.IsGenericType() ? destParamType.GetTypeInfo().GenericTypeArguments[0] : destParamType
let typeMap = _configurationProvider.ResolveTypeMap(a, sourceParamType)
where typeMap != null
let oldParam = t
let newParam = Parameter(a, oldParam.Name)
select new MappingVisitor(_configurationProvider, typeMap, oldParam, newParam, this))
.Cast<ExpressionVisitor>()
.ToList();
return visitors.Aggregate(expression as Expression, (e, v) => v.Visit(e));
}
protected override Expression VisitMember(MemberExpression node)
{
if (node == _oldParam)
return _newParam;
var propertyMap = PropertyMap(node);
if (propertyMap == null)
{
if (node.Expression is MemberExpression)
return GetConvertedSubMemberCall(node);
return node;
}
var constantVisitor = new IsConstantExpressionVisitor();
constantVisitor.Visit(node);
if (constantVisitor.IsConstant)
return node;
SetSorceSubTypes(propertyMap);
var replacedExpression = Visit(node.Expression);
if (replacedExpression == node.Expression)
replacedExpression = _parentMappingVisitor.Visit(node.Expression);
if (propertyMap.CustomExpression != null)
return propertyMap.CustomExpression.ReplaceParameters(replacedExpression);
Func<Expression, MemberInfo, Expression> getExpression = MakeMemberAccess;
return propertyMap.SourceMembers
.Aggregate(replacedExpression, getExpression);
}
private class IsConstantExpressionVisitor : ExpressionVisitor
{
public bool IsConstant { get; private set; }
protected override Expression VisitConstant(ConstantExpression node)
{
IsConstant = true;
return base.VisitConstant(node);
}
}
private Expression GetConvertedSubMemberCall(MemberExpression node)
{
var baseExpression = Visit(node.Expression);
var propertyMap = FindPropertyMapOfExpression(node.Expression as MemberExpression);
if (propertyMap == null)
return node;
var sourceType = GetSourceType(propertyMap);
var destType = propertyMap.DestinationPropertyType;
if (sourceType == destType)
return MakeMemberAccess(baseExpression, node.Member);
var typeMap = _configurationProvider.ResolveTypeMap(sourceType, destType);
var subVisitor = new MappingVisitor(_configurationProvider, typeMap, node.Expression, baseExpression, this);
var newExpression = subVisitor.Visit(node);
_destSubTypes = _destSubTypes.Concat(subVisitor._destSubTypes).ToArray();
return newExpression;
}
private Type GetSourceType(PropertyMap propertyMap) =>
propertyMap.SourceType ??
throw new AutoMapperMappingException(
"Could not determine source property type. Make sure the property is mapped.",
null,
new TypePair(null, propertyMap.DestinationPropertyType),
propertyMap.TypeMap,
propertyMap);
private PropertyMap FindPropertyMapOfExpression(MemberExpression expression)
{
var propertyMap = PropertyMap(expression);
return propertyMap == null && expression.Expression is MemberExpression
? FindPropertyMapOfExpression((MemberExpression) expression.Expression)
: propertyMap;
}
private PropertyMap PropertyMap(MemberExpression node)
=> _typeMap == null
? null
: (node.Member.IsStatic()
? null
: (!node.Member.DeclaringType.IsAssignableFrom(_typeMap.DestinationType)
? null
: _typeMap.GetExistingPropertyMapFor(node.Member)));
private void SetSorceSubTypes(PropertyMap propertyMap)
{
if (propertyMap.SourceMember is PropertyInfo info)
_destSubTypes = info.PropertyType.GetTypeInfo().GenericTypeArguments.Concat(new[] { info.PropertyType }).ToList();
else if (propertyMap.SourceMember is FieldInfo fInfo)
_destSubTypes = fInfo.FieldType.GetTypeInfo().GenericTypeArguments;
}
}
}
}