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
3 changes: 3 additions & 0 deletions src/AutoMapper/Configuration/PrimitiveExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ namespace AutoMapper.Configuration
{
internal static class PrimitiveExtensions
{
public static bool IsSetType(this Type type)
=> type.ImplementsGenericInterface(typeof(ISet<>));

public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
=> PrimitiveHelper.GetOrDefault(dictionary, key);

Expand Down
82 changes: 52 additions & 30 deletions src/AutoMapper/Execution/TypeMapPlanBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -580,49 +580,71 @@ 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);
return NullCheckSource(profileMap, sourceParameter, destinationParameter, objectMapperExpression, propertyMap);
}

public static Expression NullCheckSource(ProfileMap profileMap, Expression sourceParameter, Expression destinationParameter, Expression objectMapperExpression)
public static Expression NullCheckSource(ProfileMap profileMap, Expression sourceParameter, Expression destinationParameter, Expression objectMapperExpression, PropertyMap propertyMap = null)
{
var destinationType = destinationParameter.Type;
var defaultDestination = DefaultDestination(destinationType, profileMap);
var ifSourceNull = destinationType.IsCollectionType() ? Block(ClearDestinationCollection(destinationParameter), defaultDestination) : defaultDestination;
Expression defaultDestination;
if(propertyMap == null)
{
defaultDestination = destinationParameter.IfNullElse(DefaultDestination(destinationType, profileMap), destinationParameter);
}
else
{
defaultDestination = propertyMap.UseDestinationValue ? destinationParameter : DefaultDestination(destinationType, profileMap);
}
var ifSourceNull = destinationType.IsCollectionType() ? ClearDestinationCollection() : 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);
Expression ClearDestinationCollection()
{
var destinationElementType = ElementTypeHelper.GetElementType(destinationParameter.Type);
var destinationCollectionType = typeof(ICollection<>).MakeGenericType(destinationElementType);
var destinationVariable = Variable(destinationCollectionType, "collectionDestination");
var clearMethod = destinationCollectionType.GetDeclaredMethod("Clear");
var clear = Condition(Property(destinationVariable, "IsReadOnly"), Empty(), Call(destinationVariable, clearMethod));
return Block(new[] { destinationVariable },
Assign(destinationVariable, ToType(destinationParameter, destinationCollectionType)),
destinationVariable.IfNullElse(Empty(), clear),
defaultDestination);
}
}

private static Expression DefaultDestination(Type destinationType, ProfileMap profileMap)
{
var defaultValue = Default(destinationType);
if(!profileMap.AllowNullCollections)
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;
}
if(destinationType.IsArray)
{
var destinationElementType = ElementTypeHelper.GetElementType(destinationType);
return NewArrayBounds(destinationElementType, Enumerable.Repeat(Constant(0), destinationType.GetArrayRank()));
}
if(destinationType.IsDictionaryType())
{
return CreateCollection(typeof(Dictionary<,>));
}
if(destinationType.IsSetType())
{
return CreateCollection(typeof(HashSet<>));
}
if(destinationType.IsCollectionType())
{
return CreateCollection(typeof(List<>));
}
return defaultValue;
Expression CreateCollection(Type collectionType)
{
var concreteDestinationType =
destinationType.IsInterface() ?
collectionType.MakeGenericType(destinationType.GetGenericArguments()) :
destinationType;
var constructor = DelegateFactory.GenerateNonNullConstructorExpression(concreteDestinationType);
return ToType(constructor, destinationType);
}
}

private static Expression ObjectMapperExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, TypePair typePair, Expression sourceParameter, Expression contextParameter, PropertyMap propertyMap, Expression destinationParameter)
Expand Down
2 changes: 1 addition & 1 deletion src/AutoMapper/Internal/ExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ 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.Type.IsNullableType() ? (Expression) Constant(false) : Equal(expression, Constant(null));
return Condition(isNull, then, Convert(@else ?? Default(then.Type), then.Type));
return Condition(isNull, then, ToType(@else ?? Default(then.Type), then.Type));
}

internal class IfNotNullVisitor : ExpressionVisitor
Expand Down
2 changes: 1 addition & 1 deletion src/AutoMapper/Mappers/HashSetMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public bool IsMatch(TypePair context)
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression)
=> MapCollectionExpression(configurationProvider, profileMap, propertyMap, sourceExpression, destExpression, contextExpression, IfNotNull, typeof(HashSet<>), MapItemExpr);

private static bool IsSetType(Type type) => type.ImplementsGenericInterface(typeof(ISet<>));
private static bool IsSetType(Type type) => type.IsSetType();
}
}
8 changes: 5 additions & 3 deletions src/UnitTests/Mappers/NameValueCollectionMapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,16 @@ public void ReturnsIsFalseWhenSourceTypeIsNotNameValueCollection()
public class Map
{
[Fact]
public void ReturnsNullIfSourceValueIsNull()
public void ReturnsTheDestinationWhenPassedOne()
{
var config = new MapperConfiguration(_ => { });
IMapper mapper = new Mapper(config);

var result = mapper.Map((NameValueCollection)null, new NameValueCollection());
var destination = new NameValueCollection();

result.ShouldBeNull();
var result = mapper.Map((NameValueCollection)null, destination);

result.ShouldBeSameAs(destination);
}

[Fact]
Expand Down
22 changes: 22 additions & 0 deletions src/UnitTests/NullBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,31 @@
using Should;
using Xunit;
using System.Collections;
using System;

namespace AutoMapper.UnitTests.NullBehavior
{
public class When_mappping_null_list_to_ICollection : AutoMapperSpecBase
{
class Source
{
public List<int> Collection { get; set; }
}

class Destination
{
public ICollection<int> Collection { get; set; }
}

protected override MapperConfiguration Configuration => new MapperConfiguration(cfg => cfg.CreateMap<Source, Destination>());

[Fact]
public void Should_map_to_non_null()
{
Mapper.Map<Destination>(new Source()).Collection.ShouldNotBeNull();
}
}

public class When_resolving_untyped_null : AutoMapperSpecBase
{
class Source
Expand Down