Skip to content

Commit a7d45dc

Browse files
authored
Merge pull request #2115 from lbargaoanu/NullChecks
Null checks
2 parents 1613db6 + b8c8420 commit a7d45dc

15 files changed

+102
-85
lines changed

src/AutoMapper/Execution/TypeMapPlanBuilder.cs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace AutoMapper.Execution
1010
{
11+
using AutoMapper.Mappers.Internal;
1112
using static Expression;
1213
using static ExpressionFactory;
1314

@@ -501,14 +502,14 @@ public Expression MapExpression(TypePair typePair, Expression sourceParameter, P
501502

502503
public static Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, TypePair typePair, Expression sourceParameter, Expression contextParameter, PropertyMap propertyMap = null, Expression destinationParameter = null)
503504
{
504-
if (destinationParameter == null)
505+
if(destinationParameter == null)
505506
{
506507
destinationParameter = Default(typePair.DestinationType);
507508
}
508509
var typeMap = configurationProvider.ResolveTypeMap(typePair);
509-
if (typeMap != null)
510+
if(typeMap != null)
510511
{
511-
if (!typeMap.HasDerivedTypesToInclude())
512+
if(!typeMap.HasDerivedTypesToInclude())
512513
{
513514
typeMap.Seal(configurationProvider);
514515

@@ -518,8 +519,56 @@ public static Expression MapExpression(IConfigurationProvider configurationProvi
518519
}
519520
return ContextMap(typePair, sourceParameter, contextParameter, destinationParameter);
520521
}
522+
var objectMapperExpression = ObjectMapperExpression(configurationProvider, profileMap, typePair, sourceParameter, contextParameter, propertyMap, destinationParameter);
523+
return NullCheckSource(profileMap, sourceParameter, destinationParameter, objectMapperExpression);
524+
}
525+
526+
public static Expression NullCheckSource(ProfileMap profileMap, Expression sourceParameter, Expression destinationParameter, Expression objectMapperExpression)
527+
{
528+
var destinationType = destinationParameter.Type;
529+
var defaultDestination = DefaultDestination(destinationType, profileMap);
530+
var ifSourceNull = destinationType.IsCollectionType() ? Block(ClearDestinationCollection(destinationParameter), defaultDestination) : defaultDestination;
531+
return sourceParameter.IfNullElse(ifSourceNull, objectMapperExpression);
532+
}
533+
534+
private static Expression ClearDestinationCollection(Expression destinationParameter)
535+
{
536+
var destinationElementType = ElementTypeHelper.GetElementType(destinationParameter.Type);
537+
var destinationCollectionType = typeof(ICollection<>).MakeGenericType(destinationElementType);
538+
var clearMethod = destinationCollectionType.GetDeclaredMethod("Clear");
539+
var collection = ToType(destinationParameter, destinationCollectionType);
540+
var clear = Condition(Property(collection, "IsReadOnly"), Empty(), Call(collection, clearMethod));
541+
return collection.IfNullElse(Empty(), clear);
542+
}
543+
544+
private static Expression DefaultDestination(Type destinationType, ProfileMap profileMap)
545+
{
546+
var defaultValue = Default(destinationType);
547+
if(!profileMap.AllowNullCollections)
548+
{
549+
if(destinationType.IsArray)
550+
{
551+
var destinationElementType = ElementTypeHelper.GetElementType(destinationType);
552+
return NewArrayBounds(destinationElementType, Enumerable.Repeat(Constant(0), destinationType.GetArrayRank()));
553+
}
554+
if(destinationType.IsDictionaryType())
555+
{
556+
return destinationType.IsInterface() ?
557+
DelegateFactory.GenerateNonNullConstructorExpression(typeof(Dictionary<,>).MakeGenericType(destinationType.GetGenericArguments())) :
558+
defaultValue;
559+
}
560+
if(destinationType.IsCollectionType() && !destinationType.IsInterface())
561+
{
562+
return DelegateFactory.GenerateNonNullConstructorExpression(destinationType);
563+
}
564+
}
565+
return defaultValue;
566+
}
567+
568+
private static Expression ObjectMapperExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, TypePair typePair, Expression sourceParameter, Expression contextParameter, PropertyMap propertyMap, Expression destinationParameter)
569+
{
521570
var match = configurationProvider.GetMappers().FirstOrDefault(m => m.IsMatch(typePair));
522-
if (match != null)
571+
if(match != null)
523572
{
524573
var mapperExpression = match.MapExpression(configurationProvider, profileMap, propertyMap, sourceParameter, destinationParameter, contextParameter);
525574

src/AutoMapper/Internal/ExpressionFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ public static Expression IfNotNull(Expression expression, Type destinationType)
138138

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

145145
internal class IfNotNullVisitor : ExpressionVisitor

src/AutoMapper/MapperConfiguration.cs

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
using AutoMapper.QueryableExtensions.Impl;
1010

1111
namespace AutoMapper
12-
{
12+
{
13+
using AutoMapper.Execution;
1314
using static Expression;
1415
using static ExpressionFactory;
1516
using UntypedMapperFunc = Func<object, object, ResolutionContext, object>;
@@ -141,34 +142,28 @@ private LambdaExpression GenerateObjectMapperExpression(MapRequest mapRequest, I
141142
var source = Parameter(mapRequest.RequestedTypes.SourceType, "source");
142143
var destination = Parameter(destinationType, "mapperDestination");
143144
var context = Parameter(typeof(ResolutionContext), "context");
144-
LambdaExpression fullExpression;
145+
Expression fullExpression;
145146
if (mapperToUse == null)
146147
{
147148
var message = Constant("Missing type map configuration or unsupported mapping.");
148-
fullExpression = Lambda(Block(Throw(New(ExceptionConstructor, message, Constant(null, typeof(Exception)), Constant(mapRequest.RequestedTypes))), Default(destinationType)), source, destination, context);
149+
fullExpression = Block(Throw(New(ExceptionConstructor, message, Constant(null, typeof(Exception)), Constant(mapRequest.RequestedTypes))), Default(destinationType));
149150
}
150151
else
151152
{
152153
var map = mapperToUse.MapExpression(mapperConfiguration, Configuration, null,
153154
ToType(source, mapRequest.RuntimeTypes.SourceType),
154155
ToType(destination, mapRequest.RuntimeTypes.DestinationType),
155156
context);
156-
var mapToDestination = Lambda(ToType(map, destinationType), source, destination, context);
157-
fullExpression = TryCatch(mapToDestination, source, destination, context, mapRequest.RequestedTypes);
158-
}
159-
return fullExpression;
157+
var exception = Parameter(typeof(Exception), "ex");
158+
fullExpression =
159+
TryCatch(ToType(map, destinationType),
160+
MakeCatchBlock(typeof(Exception), exception, Block(
161+
Throw(New(ExceptionConstructor, Constant("Error mapping types."), exception, Constant(mapRequest.RequestedTypes))),
162+
Default(destination.Type)), null));
163+
}
164+
var nullCheckSource = TypeMapPlanBuilder.NullCheckSource(Configuration, source, destination, fullExpression);
165+
return Lambda(nullCheckSource, source, destination, context);
160166
}
161-
162-
private static LambdaExpression TryCatch(LambdaExpression mapExpression, ParameterExpression source, ParameterExpression destination, ParameterExpression context, TypePair types)
163-
{
164-
var exception = Parameter(typeof(Exception), "ex");
165-
166-
return Lambda(Expression.TryCatch(mapExpression.Body,
167-
MakeCatchBlock(typeof(Exception), exception, Block(
168-
Throw(New(ExceptionConstructor, Constant("Error mapping types."), exception, Constant(types))),
169-
Default(destination.Type)), null)),
170-
source, destination, context);
171-
}
172167

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

src/AutoMapper/Mappers/ArrayMapper.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ public override Expression MapExpression(IConfigurationProvider configurationPro
2121
var sourceElementType = ElementTypeHelper.GetElementType(sourceExpression.Type);
2222
var destElementType = ElementTypeHelper.GetElementType(destExpression.Type);
2323

24-
var ifNullExpr = profileMap.AllowNullCollections
25-
? (Expression) Constant(null, destExpression.Type)
26-
: NewArrayBounds(destElementType, Constant(0));
27-
2824
var itemExpr = MapItemExpr(configurationProvider, profileMap, propertyMap, sourceExpression.Type, destExpression.Type, contextExpression, out ParameterExpression itemParam);
2925

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

3834
var countParam = Parameter(typeof(int), "count");
39-
var arrayParam = Parameter(ifNullExpr.Type, "destinationArray");
35+
var arrayParam = Parameter(destExpression.Type, "destinationArray");
4036
var indexParam = Parameter(typeof(int), "destinationArrayIndex");
4137

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

58-
var mapExpr = Block(parameters, actions);
59-
60-
// return (source == null) ? ifNullExpr : Map<TSourceElement, TDestElement>(source, context);
61-
return Condition(Equal(sourceExpression, Constant(null)), ifNullExpr, mapExpr);
54+
return Block(parameters, actions);
6255
}
6356
}
6457
}

src/AutoMapper/Mappers/ConvertMapper.cs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,10 @@ from destinationType in primitiveTypes.Concat(from type in primitiveTypes.Where(
3434

3535
static LambdaExpression ConvertExpression(Type sourceType, Type destinationType)
3636
{
37-
var underlyingDestinationType = UnderlyingType(destinationType, out bool nullableDestination);
37+
var underlyingDestinationType = Nullable.GetUnderlyingType(destinationType) ?? destinationType;
3838
var convertMethod = typeof(Convert).GetDeclaredMethod("To" + underlyingDestinationType.Name, new[] { sourceType });
3939
var sourceParameter = Parameter(sourceType, "source");
40-
Expression convertCall = Call(convertMethod, sourceParameter);
41-
var lambdaBody = nullableDestination && !sourceType.IsValueType() ?
42-
Condition(Equal(sourceParameter, Constant(null)), Constant(null, destinationType), ToType(convertCall, destinationType)) :
43-
convertCall;
44-
return Lambda(lambdaBody, sourceParameter);
45-
}
46-
47-
private static Type UnderlyingType(Type type, out bool nullable)
48-
{
49-
var underlyingDestinationType = Nullable.GetUnderlyingType(type);
50-
if(underlyingDestinationType == null)
51-
{
52-
nullable = false;
53-
return type;
54-
}
55-
nullable = true;
56-
return underlyingDestinationType;
40+
return Lambda(Call(convertMethod, sourceParameter), sourceParameter);
5741
}
5842

5943
public bool IsMatch(TypePair types) => _converters.ContainsKey(types);

src/AutoMapper/Mappers/EnumToEnumMapper.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ public class EnumToEnumMapper : IObjectMapper
1111
{
1212
public static TDestination Map<TSource, TDestination>(TSource source)
1313
{
14-
if (source == null)
15-
return default(TDestination);
16-
1714
var sourceEnumType = ElementTypeHelper.GetEnumerationType(typeof(TSource));
1815
var destEnumType = ElementTypeHelper.GetEnumerationType(typeof(TDestination));
1916

src/AutoMapper/Mappers/EnumToUnderlyingTypeMapper.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,11 @@ public bool IsMatch(TypePair context)
2424
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
2525
PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
2626
Expression contextExpression) =>
27-
Condition(
28-
Equal(ToObject(sourceExpression), Constant(null)),
29-
Default(destExpression.Type),
3027
ToType(
3128
Call(ChangeTypeMethod, ToObject(sourceExpression),
3229
Constant(Nullable.GetUnderlyingType(destExpression.Type) ?? destExpression.Type)),
3330
destExpression.Type
34-
));
31+
);
3532
}
3633

3734
}

src/AutoMapper/Mappers/FlagsEnumMapper.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,13 @@ public bool IsMatch(TypePair context)
2828
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
2929
PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
3030
Expression contextExpression) =>
31-
Condition(
32-
Equal(ToObject(sourceExpression), Constant(null)),
33-
Default(destExpression.Type),
3431
ToType(
3532
Call(EnumParseMethod,
3633
Constant(Nullable.GetUnderlyingType(destExpression.Type) ?? destExpression.Type),
3734
Call(sourceExpression, sourceExpression.Type.GetDeclaredMethod("ToString")),
3835
Constant(true)
3936
),
4037
destExpression.Type
41-
));
38+
);
4239
}
4340
}

src/AutoMapper/Mappers/Internal/CollectionMapperExpressionFactory.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,14 @@ public static Expression MapCollectionExpression(IConfigurationProvider configur
3737

3838
var mapExpr = Block(addItems, destination);
3939

40-
var ifNullExpr = profileMap.AllowNullCollections ? Constant(null, passedDestination.Type) : (Expression) newExpression;
4140
var clearMethod = destinationCollectionType.GetDeclaredMethod("Clear");
4241
var checkNull =
4342
Block(new[] {newExpression, passedDestination},
4443
Assign(passedDestination, destExpression),
4544
IfThenElse(condition ?? Constant(false),
4645
Block(Assign(newExpression, passedDestination), Call(newExpression, clearMethod)),
4746
Assign(newExpression, passedDestination.Type.NewExpr(ifInterfaceType))),
48-
sourceExpression.IfNullElse(ToType(ifNullExpr, passedDestination.Type), ToType(mapExpr, passedDestination.Type))
47+
ToType(mapExpr, passedDestination.Type)
4948
);
5049
if (propertyMap != null)
5150
return checkNull;

src/AutoMapper/Mappers/MultidimensionalArrayMapper.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,16 @@ public class MultidimensionalArrayMapper : IObjectMapper
1616
private static Array Map<TDestination, TSource, TSourceElement>(TSource source, ResolutionContext context, ProfileMap profileMap)
1717
where TSource : IEnumerable
1818
{
19-
if (source == null && profileMap.AllowNullCollections)
20-
return null;
21-
2219
var destElementType = ElementTypeHelper.GetElementType(typeof(TDestination));
2320

24-
if (source != null && typeof(TDestination).IsAssignableFrom(typeof(TSource)))
21+
if (typeof(TDestination).IsAssignableFrom(typeof(TSource)))
2522
{
2623
var elementTypeMap = context.ConfigurationProvider.ResolveTypeMap(typeof(TSourceElement), destElementType);
2724
if (elementTypeMap == null)
2825
return source as Array;
2926
}
3027

31-
var sourceList = (IEnumerable)source ?? new List<TSource>();
28+
var sourceList = (IEnumerable)source;
3229
var sourceArray = source as Array;
3330
var destinationArray = sourceArray == null
3431
? Array.CreateInstance(destElementType, sourceList.Cast<object>().Count())

0 commit comments

Comments
 (0)