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
57 changes: 53 additions & 4 deletions src/AutoMapper/Execution/TypeMapPlanBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace AutoMapper.Execution
{
using AutoMapper.Mappers.Internal;
using static Expression;
using static ExpressionFactory;

Expand Down Expand Up @@ -501,14 +502,14 @@ public Expression MapExpression(TypePair typePair, Expression sourceParameter, P

public static Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, TypePair typePair, Expression sourceParameter, Expression contextParameter, PropertyMap propertyMap = null, Expression destinationParameter = null)
{
if (destinationParameter == null)
if(destinationParameter == null)
{
destinationParameter = Default(typePair.DestinationType);
}
var typeMap = configurationProvider.ResolveTypeMap(typePair);
if (typeMap != null)
if(typeMap != null)
{
if (!typeMap.HasDerivedTypesToInclude())
if(!typeMap.HasDerivedTypesToInclude())
{
typeMap.Seal(configurationProvider);

Expand All @@ -518,8 +519,56 @@ public static Expression MapExpression(IConfigurationProvider configurationProvi
}
return ContextMap(typePair, sourceParameter, contextParameter, destinationParameter);
}
var objectMapperExpression = ObjectMapperExpression(configurationProvider, profileMap, typePair, sourceParameter, contextParameter, propertyMap, destinationParameter);
return NullCheckSource(profileMap, sourceParameter, destinationParameter, objectMapperExpression);
}

public static Expression NullCheckSource(ProfileMap profileMap, Expression sourceParameter, Expression destinationParameter, Expression objectMapperExpression)
{
var destinationType = destinationParameter.Type;
var defaultDestination = DefaultDestination(destinationType, profileMap);
var ifSourceNull = destinationType.IsCollectionType() ? Block(ClearDestinationCollection(destinationParameter), defaultDestination) : defaultDestination;
return sourceParameter.IfNullElse(ifSourceNull, objectMapperExpression);
}

private static Expression ClearDestinationCollection(Expression destinationParameter)
{
var destinationElementType = ElementTypeHelper.GetElementType(destinationParameter.Type);
var destinationCollectionType = typeof(ICollection<>).MakeGenericType(destinationElementType);
var clearMethod = destinationCollectionType.GetDeclaredMethod("Clear");
var collection = ToType(destinationParameter, destinationCollectionType);
var clear = Condition(Property(collection, "IsReadOnly"), Empty(), Call(collection, clearMethod));
return collection.IfNullElse(Empty(), clear);
}

private static Expression DefaultDestination(Type destinationType, ProfileMap profileMap)
{
var defaultValue = Default(destinationType);
if(!profileMap.AllowNullCollections)
{
if(destinationType.IsArray)
{
var destinationElementType = ElementTypeHelper.GetElementType(destinationType);
return NewArrayBounds(destinationElementType, Enumerable.Repeat(Constant(0), destinationType.GetArrayRank()));
}
if(destinationType.IsDictionaryType())
{
return destinationType.IsInterface() ?
DelegateFactory.GenerateNonNullConstructorExpression(typeof(Dictionary<,>).MakeGenericType(destinationType.GetGenericArguments())) :
defaultValue;
}
if(destinationType.IsCollectionType() && !destinationType.IsInterface())
{
return DelegateFactory.GenerateNonNullConstructorExpression(destinationType);
}
}
return defaultValue;
}

private static Expression ObjectMapperExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, TypePair typePair, Expression sourceParameter, Expression contextParameter, PropertyMap propertyMap, Expression destinationParameter)
{
var match = configurationProvider.GetMappers().FirstOrDefault(m => m.IsMatch(typePair));
if (match != null)
if(match != null)
{
var mapperExpression = match.MapExpression(configurationProvider, profileMap, propertyMap, sourceParameter, destinationParameter, contextParameter);

Expand Down
4 changes: 2 additions & 2 deletions src/AutoMapper/Internal/ExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ public static Expression IfNotNull(Expression expression, Type destinationType)

public static Expression IfNullElse(Expression expression, Expression then, Expression @else = null)
{
var isNull = expression.Type.IsValueType() ? (Expression) Constant(false) : Equal(expression, Constant(null));
return Condition(isNull, then, @else ?? Default(then.Type));
var isNull = expression.Type.IsValueType() && !expression.Type.IsNullableType() ? (Expression) Constant(false) : Equal(expression, Constant(null));
return Condition(isNull, then, Convert(@else ?? Default(then.Type), then.Type));
}

internal class IfNotNullVisitor : ExpressionVisitor
Expand Down
31 changes: 13 additions & 18 deletions src/AutoMapper/MapperConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
using AutoMapper.QueryableExtensions.Impl;

namespace AutoMapper
{
{
using AutoMapper.Execution;
using static Expression;
using static ExpressionFactory;
using UntypedMapperFunc = Func<object, object, ResolutionContext, object>;
Expand Down Expand Up @@ -141,34 +142,28 @@ private LambdaExpression GenerateObjectMapperExpression(MapRequest mapRequest, I
var source = Parameter(mapRequest.RequestedTypes.SourceType, "source");
var destination = Parameter(destinationType, "mapperDestination");
var context = Parameter(typeof(ResolutionContext), "context");
LambdaExpression fullExpression;
Expression fullExpression;
if (mapperToUse == null)
{
var message = Constant("Missing type map configuration or unsupported mapping.");
fullExpression = Lambda(Block(Throw(New(ExceptionConstructor, message, Constant(null, typeof(Exception)), Constant(mapRequest.RequestedTypes))), Default(destinationType)), source, destination, context);
fullExpression = Block(Throw(New(ExceptionConstructor, message, Constant(null, typeof(Exception)), Constant(mapRequest.RequestedTypes))), Default(destinationType));
}
else
{
var map = mapperToUse.MapExpression(mapperConfiguration, Configuration, null,
ToType(source, mapRequest.RuntimeTypes.SourceType),
ToType(destination, mapRequest.RuntimeTypes.DestinationType),
context);
var mapToDestination = Lambda(ToType(map, destinationType), source, destination, context);
fullExpression = TryCatch(mapToDestination, source, destination, context, mapRequest.RequestedTypes);
}
return fullExpression;
var exception = Parameter(typeof(Exception), "ex");
fullExpression =
TryCatch(ToType(map, destinationType),
MakeCatchBlock(typeof(Exception), exception, Block(
Throw(New(ExceptionConstructor, Constant("Error mapping types."), exception, Constant(mapRequest.RequestedTypes))),
Default(destination.Type)), null));
}
var nullCheckSource = TypeMapPlanBuilder.NullCheckSource(Configuration, source, destination, fullExpression);
return Lambda(nullCheckSource, source, destination, context);
}

private static LambdaExpression TryCatch(LambdaExpression mapExpression, ParameterExpression source, ParameterExpression destination, ParameterExpression context, TypePair types)
{
var exception = Parameter(typeof(Exception), "ex");

return Lambda(Expression.TryCatch(mapExpression.Body,
MakeCatchBlock(typeof(Exception), exception, Block(
Throw(New(ExceptionConstructor, Constant("Error mapping types."), exception, Constant(types))),
Default(destination.Type)), null)),
source, destination, context);
}

public TypeMap[] GetAllTypeMaps() => _typeMapRegistry.TypeMaps.ToArray();

Expand Down
11 changes: 2 additions & 9 deletions src/AutoMapper/Mappers/ArrayMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ public override Expression MapExpression(IConfigurationProvider configurationPro
var sourceElementType = ElementTypeHelper.GetElementType(sourceExpression.Type);
var destElementType = ElementTypeHelper.GetElementType(destExpression.Type);

var ifNullExpr = profileMap.AllowNullCollections
? (Expression) Constant(null, destExpression.Type)
: NewArrayBounds(destElementType, Constant(0));

var itemExpr = MapItemExpr(configurationProvider, profileMap, propertyMap, sourceExpression.Type, destExpression.Type, contextExpression, out ParameterExpression itemParam);

//var count = source.Count();
Expand All @@ -36,7 +32,7 @@ public override Expression MapExpression(IConfigurationProvider configurationPro
//return array;

var countParam = Parameter(typeof(int), "count");
var arrayParam = Parameter(ifNullExpr.Type, "destinationArray");
var arrayParam = Parameter(destExpression.Type, "destinationArray");
var indexParam = Parameter(typeof(int), "destinationArrayIndex");

var actions = new List<Expression>();
Expand All @@ -55,10 +51,7 @@ public override Expression MapExpression(IConfigurationProvider configurationPro
));
actions.Add(arrayParam);

var mapExpr = Block(parameters, actions);

// return (source == null) ? ifNullExpr : Map<TSourceElement, TDestElement>(source, context);
return Condition(Equal(sourceExpression, Constant(null)), ifNullExpr, mapExpr);
return Block(parameters, actions);
}
}
}
20 changes: 2 additions & 18 deletions src/AutoMapper/Mappers/ConvertMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,10 @@ from destinationType in primitiveTypes.Concat(from type in primitiveTypes.Where(

static LambdaExpression ConvertExpression(Type sourceType, Type destinationType)
{
var underlyingDestinationType = UnderlyingType(destinationType, out bool nullableDestination);
var underlyingDestinationType = Nullable.GetUnderlyingType(destinationType) ?? destinationType;
var convertMethod = typeof(Convert).GetDeclaredMethod("To" + underlyingDestinationType.Name, new[] { sourceType });
var sourceParameter = Parameter(sourceType, "source");
Expression convertCall = Call(convertMethod, sourceParameter);
var lambdaBody = nullableDestination && !sourceType.IsValueType() ?
Condition(Equal(sourceParameter, Constant(null)), Constant(null, destinationType), ToType(convertCall, destinationType)) :
convertCall;
return Lambda(lambdaBody, sourceParameter);
}

private static Type UnderlyingType(Type type, out bool nullable)
{
var underlyingDestinationType = Nullable.GetUnderlyingType(type);
if(underlyingDestinationType == null)
{
nullable = false;
return type;
}
nullable = true;
return underlyingDestinationType;
return Lambda(Call(convertMethod, sourceParameter), sourceParameter);
}

public bool IsMatch(TypePair types) => _converters.ContainsKey(types);
Expand Down
3 changes: 0 additions & 3 deletions src/AutoMapper/Mappers/EnumToEnumMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ public class EnumToEnumMapper : IObjectMapper
{
public static TDestination Map<TSource, TDestination>(TSource source)
{
if (source == null)
return default(TDestination);

var sourceEnumType = ElementTypeHelper.GetEnumerationType(typeof(TSource));
var destEnumType = ElementTypeHelper.GetEnumerationType(typeof(TDestination));

Expand Down
5 changes: 1 addition & 4 deletions src/AutoMapper/Mappers/EnumToUnderlyingTypeMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,11 @@ public bool IsMatch(TypePair context)
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
Expression contextExpression) =>
Condition(
Equal(ToObject(sourceExpression), Constant(null)),
Default(destExpression.Type),
ToType(
Call(ChangeTypeMethod, ToObject(sourceExpression),
Constant(Nullable.GetUnderlyingType(destExpression.Type) ?? destExpression.Type)),
destExpression.Type
));
);
}

}
5 changes: 1 addition & 4 deletions src/AutoMapper/Mappers/FlagsEnumMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,13 @@ public bool IsMatch(TypePair context)
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
Expression contextExpression) =>
Condition(
Equal(ToObject(sourceExpression), Constant(null)),
Default(destExpression.Type),
ToType(
Call(EnumParseMethod,
Constant(Nullable.GetUnderlyingType(destExpression.Type) ?? destExpression.Type),
Call(sourceExpression, sourceExpression.Type.GetDeclaredMethod("ToString")),
Constant(true)
),
destExpression.Type
));
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,14 @@ public static Expression MapCollectionExpression(IConfigurationProvider configur

var mapExpr = Block(addItems, destination);

var ifNullExpr = profileMap.AllowNullCollections ? Constant(null, passedDestination.Type) : (Expression) newExpression;
var clearMethod = destinationCollectionType.GetDeclaredMethod("Clear");
var checkNull =
Block(new[] {newExpression, passedDestination},
Assign(passedDestination, destExpression),
IfThenElse(condition ?? Constant(false),
Block(Assign(newExpression, passedDestination), Call(newExpression, clearMethod)),
Assign(newExpression, passedDestination.Type.NewExpr(ifInterfaceType))),
sourceExpression.IfNullElse(ToType(ifNullExpr, passedDestination.Type), ToType(mapExpr, passedDestination.Type))
ToType(mapExpr, passedDestination.Type)
);
if (propertyMap != null)
return checkNull;
Expand Down
7 changes: 2 additions & 5 deletions src/AutoMapper/Mappers/MultidimensionalArrayMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,16 @@ public class MultidimensionalArrayMapper : IObjectMapper
private static Array Map<TDestination, TSource, TSourceElement>(TSource source, ResolutionContext context, ProfileMap profileMap)
where TSource : IEnumerable
{
if (source == null && profileMap.AllowNullCollections)
return null;

var destElementType = ElementTypeHelper.GetElementType(typeof(TDestination));

if (source != null && typeof(TDestination).IsAssignableFrom(typeof(TSource)))
if (typeof(TDestination).IsAssignableFrom(typeof(TSource)))
{
var elementTypeMap = context.ConfigurationProvider.ResolveTypeMap(typeof(TSourceElement), destElementType);
if (elementTypeMap == null)
return source as Array;
}

var sourceList = (IEnumerable)source ?? new List<TSource>();
var sourceList = (IEnumerable)source;
var sourceArray = source as Array;
var destinationArray = sourceArray == null
? Array.CreateInstance(destElementType, sourceList.Cast<object>().Count())
Expand Down
3 changes: 0 additions & 3 deletions src/AutoMapper/Mappers/NameValueCollectionMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ public class NameValueCollectionMapper : IObjectMapper
{
private static NameValueCollection Map(NameValueCollection source)
{
if (source == null)
return null;

var nvc = new NameValueCollection();
foreach (var s in source.AllKeys)
nvc.Add(s, source[s]);
Expand Down
6 changes: 1 addition & 5 deletions src/AutoMapper/Mappers/NullableSourceMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,13 @@ public class NullableSourceMapper : IObjectMapperInfo
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
Expression contextExpression) =>
Condition(
Property(sourceExpression, sourceExpression.Type.GetDeclaredProperty("HasValue")),
TypeMapPlanBuilder.MapExpression(configurationProvider, profileMap,
new TypePair(Nullable.GetUnderlyingType(sourceExpression.Type), destExpression.Type),
Property(sourceExpression, sourceExpression.Type.GetDeclaredProperty("Value")),
contextExpression,
propertyMap,
destExpression
),
DelegateFactory.GenerateConstructorExpression(destExpression.Type, profileMap)
);
);

public TypePair GetAssociatedTypes(TypePair initialTypes)
{
Expand Down
5 changes: 1 addition & 4 deletions src/AutoMapper/Mappers/StringMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ public class StringMapper : IObjectMapper

public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression)
{
var toStringCall = Call(sourceExpression, typeof(object).GetDeclaredMethod("ToString"));
return sourceExpression.Type.IsValueType()
? (Expression) toStringCall
: Condition(Equal(sourceExpression, Constant(null)), Constant(null, typeof(string)), toStringCall);
return Call(sourceExpression, typeof(object).GetDeclaredMethod("ToString"));
}
}
}
5 changes: 1 addition & 4 deletions src/AutoMapper/Mappers/UnderlyingTypeToEnumMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,10 @@ public bool IsMatch(TypePair context)
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
Expression contextExpression) =>
Condition(
Equal(ToObject(sourceExpression), Constant(null)),
Default(destExpression.Type),
ToType(
Call(EnumToObject, Constant(Nullable.GetUnderlyingType(destExpression.Type) ?? destExpression.Type),
ToObject(sourceExpression)),
destExpression.Type
));
);
}
}
Loading