Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("AutoMapper.Extensions.ExpressionMapping.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010079dfef85ed6ba841717e154f13182c0a6029a40794a6ecd2886c7dc38825f6a4c05b0622723a01cd080f9879126708eef58f134accdc99627947425960ac2397162067507e3c627992aa6b92656ad3380999b30b5d5645ba46cc3fcc6a1de5de7afebcf896c65fb4f9547a6c0c6433045fceccb1fa15e960d519d0cd694b29a4")]

namespace AutoMapper.Extensions.ExpressionMapping
{
Expand All @@ -15,7 +18,7 @@ public static Type CreateAnonymousType(IEnumerable<MemberInfo> memberDetails)

public static Type CreateAnonymousType(IDictionary<string, Type> memberDetails)
{
AssemblyName dynamicAssemblyName = new AssemblyName("TempAssembly.AutoMapper.Extensions.ExpressionMapping");
AssemblyName dynamicAssemblyName = new("TempAssembly.AutoMapper.Extensions.ExpressionMapping");
AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule("TempAssembly.AutoMapper.Extensions.ExpressionMapping");
TypeBuilder typeBuilder = dynamicModule.DefineType(GetAnonymousTypeName(), TypeAttributes.Public);
Expand All @@ -32,7 +35,7 @@ public static Type CreateAnonymousType(IDictionary<string, Type> memberDetails)
FieldBuilder = typeBuilder.DefineField(string.Concat("_", memberName), memberType, FieldAttributes.Private),
PropertyBuilder = typeBuilder.DefineProperty(memberName, PropertyAttributes.HasDefault, memberType, null),
GetMethodBuilder = typeBuilder.DefineMethod(string.Concat("get_", memberName), getSetAttr, memberType, Type.EmptyTypes),
SetMethodBuilder = typeBuilder.DefineMethod(string.Concat("set_", memberName), getSetAttr, null, new Type[] { memberType })
SetMethodBuilder = typeBuilder.DefineMethod(string.Concat("set_", memberName), getSetAttr, null, [memberType])
};
}
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using AutoMapper.Internal;
using AutoMapper.Mappers;

namespace AutoMapper.Extensions.ExpressionMapping
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static Type[] GetElementTypes(Type enumerableType, System.Collections.IEn
{
if (enumerableType.HasElementType)
{
return new[] { enumerableType.GetElementType() };
return [enumerableType.GetElementType()];
}

var iDictionaryType = enumerableType.GetDictionaryType();
Expand All @@ -43,7 +43,7 @@ public static Type[] GetElementTypes(Type enumerableType, System.Collections.IEn
{
var first = enumerable?.Cast<object>().FirstOrDefault();

return new[] { first?.GetType() ?? typeof(object) };
return [first?.GetType() ?? typeof(object)];
}

throw new ArgumentException($"Unable to find the element type for type '{enumerableType}'.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,14 @@
using System.Reflection;
using AutoMapper.Internal;

namespace AutoMapper
namespace AutoMapper.Extensions.ExpressionMapping
{
using AutoMapper.Extensions.ExpressionMapping;
using static Expression;

internal static class ExpressionExtensions
{
public static Expression MemberAccesses(this IEnumerable<MemberInfo> members, Expression obj) =>
members.Aggregate(obj, (expression, member) => MakeMemberAccess(expression, member));

public static IEnumerable<MemberExpression> GetMembers(this Expression expression)
{
var memberExpression = expression as MemberExpression;
if(memberExpression == null)
{
return new MemberExpression[0];
}
return memberExpression.GetMembers();
}

public static IEnumerable<MemberExpression> GetMembers(this MemberExpression expression)
{
while(expression != null)
{
yield return expression;
expression = expression.Expression as MemberExpression;
}
}

public static bool IsMemberPath(this LambdaExpression exp)
{
return exp.Body.GetMembers().LastOrDefault()?.Expression == exp.Parameters.First();
}
}

internal static class ExpressionHelpers
Expand Down
54 changes: 28 additions & 26 deletions src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using System;
using AutoMapper.Internal;
using AutoMapper.Internal.Mappers;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper.Extensions.ExpressionMapping;
using AutoMapper.Internal;
using AutoMapper.Internal.Mappers;
using static System.Linq.Expressions.Expression;

namespace AutoMapper.Mappers
namespace AutoMapper.Extensions.ExpressionMapping
{
public class ExpressionMapper : IObjectMapper
{
Expand All @@ -23,7 +23,7 @@ public bool IsMatch(TypePair context) => typeof(LambdaExpression).IsAssignableFr
&& typeof(LambdaExpression).IsAssignableFrom(context.DestinationType)
&& context.DestinationType != typeof(LambdaExpression);

public Expression MapExpression(IGlobalConfiguration configurationProvider, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression)
public Expression MapExpression(IGlobalConfiguration configurationProvider, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression)
=> Call
(
null,
Expand All @@ -37,9 +37,10 @@ public Expression MapExpression(IGlobalConfiguration configurationProvider, Prof
return null;
}

[ExcludeFromCodeCoverage]
internal class MappingVisitor : ExpressionVisitor
{
private IList<Type> _destSubTypes = new Type[0];
private IList<Type> _destSubTypes = [];

private readonly IConfigurationProvider _configurationProvider;
private readonly TypeMap _typeMap;
Expand Down Expand Up @@ -81,7 +82,7 @@ private MethodCallExpression GetConvertedMethodCall(MethodCallExpression node)
return Call(convertedMethodCall, convertedArguments);
}

private static Type GetConvertingTypeIfExists(IList<Expression> args, Type t, IList<Expression> arguments)
private static Type GetConvertingTypeIfExists(System.Collections.ObjectModel.ReadOnlyCollection<Expression> args, Type t, IList<Expression> arguments)
{
var matchingArgument = args.Where(a => !a.Type.IsGenericType()).FirstOrDefault(a => a.Type == t);
if (matchingArgument != null)
Expand Down Expand Up @@ -109,7 +110,7 @@ protected override Expression VisitBinary(BinaryExpression node)
CheckNullableToNonNullableChanges(node.Left, node.Right, ref newLeft, ref newRight);
CheckNullableToNonNullableChanges(node.Right, node.Left, ref newRight, ref newLeft);
return MakeBinary(node.NodeType, newLeft, newRight);
bool IsNullConstant(Expression expression) => expression is ConstantExpression constant && constant.Value == null;
static bool IsNullConstant(Expression expression) => expression is ConstantExpression constant && constant.Value == null;
}

private static void CheckNullableToNonNullableChanges(Expression left, Expression right, ref Expression newLeft, ref Expression newRight)
Expand Down Expand Up @@ -145,30 +146,30 @@ private static void UpdateToNonNullableExpression(Expression right, out Expressi
: right.Type;
newRight = Constant(expression.Value, t);
}
else if (right is UnaryExpression)
newRight = ((UnaryExpression) right).Operand;
else if (right is UnaryExpression unaryExpression)
newRight = unaryExpression.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)
private static bool GoingFromNonNullableToNullable(Expression node, Expression newLeft)
=> !node.Type.IsNullableType() && newLeft.Type.IsNullableType();

private static bool BothAreNullable(Expression node, Expression newLeft)
private static bool BothAreNullable(Expression node, Expression newLeft)
=> node.Type.IsNullableType() && newLeft.Type.IsNullableType();

private static bool BothAreNonNullable(Expression node, Expression newLeft)
private static bool BothAreNonNullable(Expression node, Expression newLeft)
=> !node.Type.IsNullableType() && !newLeft.Type.IsNullableType();

protected override Expression VisitLambda<T>(Expression<T> node)
{
return node.Parameters.Any(b => b.Type == _oldParam.Type)
? VisitLambdaExpression(node)
return node.Parameters.Any(b => b.Type == _oldParam.Type)
? VisitLambdaExpression(node)
: VisitAllParametersExpression(node);
}

private Expression VisitLambdaExpression<T>(Expression<T> expression)
private LambdaExpression VisitLambdaExpression<T>(Expression<T> expression)
{
var convertedBody = Visit(expression.Body);
var convertedArguments = expression.Parameters.Select(e => Visit(e) as ParameterExpression).ToList();
Expand Down Expand Up @@ -226,6 +227,7 @@ protected override Expression VisitMember(MemberExpression node)
.Aggregate(replacedExpression, getExpression);
}

[ExcludeFromCodeCoverage]
private class IsConstantExpressionVisitor : ExpressionVisitor
{
public bool IsConstant { get; private set; }
Expand All @@ -251,22 +253,22 @@ private Expression GetConvertedSubMemberCall(MemberExpression node)
var typeMap = _configurationProvider.Internal().ResolveTypeMap(sourceType, destType);
var subVisitor = new MappingVisitor(_configurationProvider, typeMap, node.Expression, baseExpression, this);
var newExpression = subVisitor.Visit(node);
_destSubTypes = _destSubTypes.Concat(subVisitor._destSubTypes).ToArray();
_destSubTypes = [.. _destSubTypes, .. subVisitor._destSubTypes];
return newExpression;
}

private Type GetSourceType(PropertyMap propertyMap) =>
private static Type GetSourceType(PropertyMap propertyMap) =>
propertyMap.SourceType ??
throw new AutoMapperMappingException(
"Could not determine source property type. Make sure the property is mapped.",
null,
"Could not determine source property type. Make sure the property is mapped.",
null,
propertyMap);

private PropertyMap FindPropertyMapOfExpression(MemberExpression expression)
{
var propertyMap = PropertyMap(expression);
return propertyMap == null && expression.Expression is MemberExpression
? FindPropertyMapOfExpression((MemberExpression) expression.Expression)
return propertyMap == null && expression.Expression is MemberExpression memberExpression
? FindPropertyMapOfExpression(memberExpression)
: propertyMap;
}

Expand All @@ -277,9 +279,9 @@ private PropertyMap PropertyMap(MemberExpression node)
? null
: (!node.Member.DeclaringType.IsAssignableFrom(_typeMap.DestinationType)
? null
: GetExistingPropertyMapFor(node.Member, _typeMap)));
: MappingVisitor.GetExistingPropertyMapFor(node.Member, _typeMap)));

private PropertyMap GetExistingPropertyMapFor(MemberInfo destinationProperty, TypeMap typeMap)
private static PropertyMap GetExistingPropertyMapFor(MemberInfo destinationProperty, TypeMap typeMap)
{
if (!destinationProperty.DeclaringType.IsAssignableFrom(typeMap.DestinationType))
return null;
Expand All @@ -290,7 +292,7 @@ private PropertyMap GetExistingPropertyMapFor(MemberInfo destinationProperty, Ty
private void SetSourceSubTypes(PropertyMap propertyMap)
{
if (propertyMap.SourceMember is PropertyInfo info)
_destSubTypes = info.PropertyType.GetTypeInfo().GenericTypeArguments.Concat(new[] { info.PropertyType }).ToList();
_destSubTypes = [.. info.PropertyType.GetTypeInfo().GenericTypeArguments, info.PropertyType];
else if (propertyMap.SourceMember is FieldInfo fInfo)
_destSubTypes = fInfo.FieldType.GetTypeInfo().GenericTypeArguments;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,6 @@ namespace AutoMapper.Extensions.ExpressionMapping.Extensions
{
internal static class VisitorExtensions
{
/// <summary>
/// Returns true if the expression is a direct or descendant member expression of the parameter.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
public static bool IsMemberExpression(this Expression expression)
{
if (expression.NodeType == ExpressionType.MemberAccess)
{
var memberExpression = (MemberExpression)expression;
return IsMemberOrParameterExpression(memberExpression.Expression);
}

return false;
}

private static bool IsMemberOrParameterExpression(Expression expression)
{
//the node represents parameter of the expression
switch (expression.NodeType)
{
case ExpressionType.Parameter:
return true;
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression)expression;
return IsMemberOrParameterExpression(memberExpression.Expression);
}

return false;
}

/// <summary>
/// Returns the fully qualified name of the member starting with the immediate child member of the parameter
/// </summary>
Expand Down Expand Up @@ -70,15 +39,11 @@ public static string GetPropertyFullName(this Expression expression)

public static Expression GetUnconvertedExpression(this Expression expression)
{
switch (expression.NodeType)
return expression.NodeType switch
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
case ExpressionType.TypeAs:
return ((UnaryExpression)expression).Operand.GetUnconvertedExpression();
default:
return expression;
}
ExpressionType.Convert or ExpressionType.ConvertChecked or ExpressionType.TypeAs => ((UnaryExpression)expression).Operand.GetUnconvertedExpression(),
_ => expression,
};
}

public static Expression ConvertTypeIfNecessary(this Expression expression, Type memberType)
Expand Down Expand Up @@ -136,9 +101,7 @@ public static ParameterExpression GetParameterExpression(this Expression express
if (isExtension && parentExpression == null && methodExpression.Arguments.Count > 0)
parentExpression = methodExpression.Arguments[0];//Method is an extension method based on the type of methodExpression.Arguments[0].

return isExtension && parentExpression == null && methodExpression.Arguments.Count > 0
? GetParameterExpression(methodExpression.Arguments[0])
: GetParameterExpression(parentExpression);
return GetParameterExpression(parentExpression);
}

return null;
Expand Down Expand Up @@ -177,33 +140,19 @@ public static string GetMemberFullName(this LambdaExpression expr)
{
if (expr.Body.NodeType == ExpressionType.Parameter)
return string.Empty;

MemberExpression me;
switch (expr.Body.NodeType)
MemberExpression me = expr.Body.NodeType switch
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
case ExpressionType.TypeAs:
me = expr.Body.GetUnconvertedExpression() as MemberExpression;
break;
default:
me = expr.Body as MemberExpression;
break;
}

ExpressionType.Convert or ExpressionType.ConvertChecked or ExpressionType.TypeAs => expr.Body.GetUnconvertedExpression() as MemberExpression,
_ => expr.Body as MemberExpression,
};
return me.GetPropertyFullName();
}

/// <summary>
/// Returns the underlying type typeof(T) when the type implements IEnumerable.
/// Determines whether the specified type is an enumeration type.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static List<Type> GetUnderlyingGenericTypes(this Type type) =>
type == null || !type.GetTypeInfo().IsGenericType
? new List<Type>()
: type.GetGenericArguments().ToList();

/// <param name="type">The type to evaluate. This can be a nullable type, in which case the underlying type is checked.</param>
/// <returns>true if the specified type is an enumeration; otherwise, false.</returns>
public static bool IsEnumType(this Type type)
{
if (type.IsNullableType())
Expand Down
Loading