Skip to content

Commit df219b2

Browse files
authored
Merge pull request #3821 from AutoMapper/SO
2 parents 89c4fce + f5b6a3f commit df219b2

File tree

7 files changed

+88
-23
lines changed

7 files changed

+88
-23
lines changed

docs/Flattening.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,11 @@ destination.Name.ShouldBe("name");
145145
destination.Description.ShouldBe("description");
146146
destination.Title.ShouldBe("title");
147147
```
148-
So this allows you to reuse the configuration in the existing maps for the child types `InnerSource` and `OtherInnerSource` when mapping the parent types `Source` and `Destination`. It works in a similar way to [mapping inheritance](Mapping-inheritance.html), but it uses composition, not inheritance.
148+
So this allows you to reuse the configuration in the existing map for the child types `InnerSource` and `OtherInnerSource` when mapping the parent types `Source` and `Destination`. It works in a similar way to [mapping inheritance](Mapping-inheritance.html), but it uses composition, not inheritance.
149149

150150
The order of the parameters in the `IncludeMembers` call is relevant. When mapping a destination member, the first match wins, starting with the source object itself and then with the included child objects in the order you specified. So in the example above, `Name` is mapped from the source object itself and `Description` from `InnerSource` because it's the first match.
151151

152-
Note that this matching is static, it happens at configuration time, not at `Map` time, and the runtime types of the child objects are not considered.
152+
Note that this matching is static, it happens at configuration time, not at `Map` time, so the runtime types of the child objects are not considered.
153153

154154
IncludeMembers integrates with `ReverseMap`. An included member will be reversed to
155155
```c#

src/AutoMapper/Configuration/ConfigurationValidator.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ private void DryRunTypeMap(ICollection<TypeMap> typeMapsChecked, TypePair types,
131131
if(mapperToUse is IObjectMapperInfo mapperInfo)
132132
{
133133
var newTypePair = mapperInfo.GetAssociatedTypes(types);
134-
DryRunTypeMap(typeMapsChecked, newTypePair, null, memberMap);
134+
if (newTypePair != types)
135+
{
136+
DryRunTypeMap(typeMapsChecked, newTypePair, null, memberMap);
137+
}
135138
}
136139
}
137140
}

src/AutoMapper/Configuration/MapperConfiguration.cs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -300,25 +300,15 @@ private TypeMap GetTypeMap(TypePair initialTypes)
300300
static List<Type> GetTypeInheritance(Type type)
301301
{
302302
var interfaces = type.GetInterfaces();
303-
var lastIndex = interfaces.Length - 1;
304303
var types = new List<Type>(interfaces.Length + 2) { type };
305304
Type baseType = type;
306305
while ((baseType = baseType.BaseType) != null)
307306
{
308307
types.Add(baseType);
309-
foreach (var interfaceType in baseType.GetInterfaces())
310-
{
311-
var interfaceIndex = Array.LastIndexOf(interfaces, interfaceType);
312-
if (interfaceIndex != lastIndex)
313-
{
314-
interfaces[interfaceIndex] = interfaces[lastIndex];
315-
interfaces[lastIndex] = interfaceType;
316-
}
317-
}
318308
}
319-
foreach (var interfaceType in interfaces)
309+
for(int index = interfaces.Length - 1; index >= 0; index--)
320310
{
321-
types.Add(interfaceType);
311+
types.Add(interfaces[index]);
322312
}
323313
return types;
324314
}

src/AutoMapper/Internal/TypeExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ public static Type GetGenericInterface(this Type type, Type genericInterface)
7777
{
7878
return type;
7979
}
80-
foreach (var interfaceType in type.GetInterfaces())
80+
var interfaces = type.GetInterfaces();
81+
for(int index = interfaces.Length - 1; index >= 0; index--)
8182
{
83+
var interfaceType = interfaces[index];
8284
if (interfaceType.IsGenericType(genericInterface))
8385
{
8486
return interfaceType;

src/AutoMapper/Mappers/CollectionMapper.cs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,22 @@ Expression MapReadOnlyCollection(Type genericCollectionType, Type genericReadOnl
4747
Expression MapCollectionCore(Expression destExpression)
4848
{
4949
var destinationType = destExpression.Type;
50-
MethodInfo addMethod;
51-
bool isIList, mustUseDestination = memberMap is { MustUseDestination: true };
52-
Type destinationCollectionType, destinationElementType;
50+
var sourceType = sourceExpression.Type;
51+
MethodInfo addMethod = null;
52+
bool isIList = false, mustUseDestination = memberMap is { MustUseDestination: true };
53+
Type destinationCollectionType = null, destinationElementType = null;
5354
GetDestinationType();
5455
var passedDestination = Variable(destExpression.Type, "passedDestination");
5556
var newExpression = Variable(passedDestination.Type, "collectionDestination");
56-
var sourceElementType = sourceExpression.Type.GetICollectionType()?.GenericTypeArguments[0] ?? GetEnumerableElementType(sourceExpression.Type);
57+
var sourceElementType = GetEnumerableElementType(sourceType);
58+
if (destinationCollectionType == null || (sourceType == sourceElementType && destinationType == destinationElementType))
59+
{
60+
if (destinationType.IsAssignableFrom(sourceType))
61+
{
62+
return sourceExpression;
63+
}
64+
throw new NotSupportedException($"Unknown collection. Consider a custom type converter from {sourceType} to {destinationType}.");
65+
}
5766
var itemParam = Parameter(sourceElementType, "item");
5867
var itemExpr = configurationProvider.MapExpression(profileMap, new TypePair(sourceElementType, destinationElementType), itemParam);
5968
Expression destination, assignNewExpression;
@@ -78,25 +87,36 @@ Expression MapCollectionCore(Expression destExpression)
7887
return CheckContext();
7988
void GetDestinationType()
8089
{
90+
var immutableCollection = !mustUseDestination && destinationType.IsValueType;
91+
if (immutableCollection)
92+
{
93+
return;
94+
}
8195
destinationCollectionType = destinationType.GetICollectionType();
82-
destinationElementType = destinationCollectionType?.GenericTypeArguments[0] ?? GetEnumerableElementType(destinationType);
8396
isIList = destExpression.Type.IsListType();
8497
if (destinationCollectionType == null)
8598
{
8699
if (isIList)
87100
{
88101
destinationCollectionType = typeof(IList);
89102
addMethod = IListAdd;
103+
destinationElementType = GetEnumerableElementType(destinationType);
90104
}
91105
else
92106
{
107+
if (!destinationType.IsInterface)
108+
{
109+
return;
110+
}
111+
destinationElementType = GetEnumerableElementType(destinationType);
93112
destinationCollectionType = typeof(ICollection<>).MakeGenericType(destinationElementType);
94113
destExpression = Convert(mustUseDestination ? destExpression : Null, destinationCollectionType);
95114
addMethod = destinationCollectionType.GetMethod("Add");
96115
}
97116
}
98117
else
99118
{
119+
destinationElementType = destinationCollectionType.GenericTypeArguments[0];
100120
addMethod = destinationCollectionType.GetMethod("Add");
101121
}
102122
}

src/AutoMapper/QueryableExtensions/ProjectionBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ bool OverMaxDepth()
106106
}
107107
void ProjectProperties()
108108
{
109-
foreach (var propertyMap in typeMap.PropertyMaps.Where(pm => pm.CanResolveValue && pm.DestinationMember.CanBeSet()).OrderBy(pm => pm.DestinationName))
109+
foreach (var propertyMap in typeMap.PropertyMaps.Where(pm => pm.CanResolveValue && pm.DestinationMember.CanBeSet()).OrderBy(pm => pm.DestinationMember.MetadataToken))
110110
{
111111
var propertyProjection = TryProjectMember(propertyMap, propertyMap.ExplicitExpansion);
112112
if (propertyProjection != null)

src/UnitTests/CollectionMapping.cs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,61 @@
66
using Xunit;
77
using Shouldly;
88
using System.Collections;
9-
using System.Reflection;
109
using AutoMapper.Internal;
10+
using System.Collections.Immutable;
1111

1212
namespace AutoMapper.UnitTests
1313
{
14+
public class ImmutableCollection : AutoMapperSpecBase
15+
{
16+
class Source
17+
{
18+
public string Value { get; set; }
19+
}
20+
class Destination
21+
{
22+
public ImmutableArray<int> Value { get; set; }
23+
}
24+
protected override MapperConfiguration CreateConfiguration() => new(c =>
25+
c.CreateMap<Source, Destination>().ForMember(d=>d.Value, o=>o.MapFrom(_=>ImmutableArray.Create<int>())));
26+
[Fact]
27+
public void Should_work() => Map<Destination>(new Source()).Value.ShouldBeOfType<ImmutableArray<int>>();
28+
}
29+
public class AssignableCollection : AutoMapperSpecBase
30+
{
31+
class Source
32+
{
33+
public string Value { get; set; }
34+
}
35+
class Destination
36+
{
37+
public MyJObject Value { get; set; }
38+
}
39+
class MyJObject : IEnumerable
40+
{
41+
public IEnumerator GetEnumerator() => throw new NotImplementedException();
42+
}
43+
protected override MapperConfiguration CreateConfiguration() => new(c =>
44+
c.CreateMap<Source, Destination>().ForMember(d=>d.Value, o=>o.MapFrom(_=>new MyJObject())));
45+
[Fact]
46+
public void Should_work() => Map<Destination>(new Source()).Value.ShouldBeOfType<MyJObject>();
47+
}
48+
public class RecursiveCollection : AutoMapperSpecBase
49+
{
50+
class Source
51+
{
52+
public string Value { get; set; }
53+
}
54+
class Destination
55+
{
56+
public MyJObject Value { get; set; }
57+
}
58+
class MyJObject : List<MyJObject>{}
59+
protected override MapperConfiguration CreateConfiguration() => new(c =>
60+
c.CreateMap<Source, Destination>().ForMember(d=>d.Value, o=>o.MapFrom(_=>new MyJObject())));
61+
[Fact]
62+
public void Should_work() => Map<Destination>(new Source()).Value.ShouldBeOfType<MyJObject>();
63+
}
1464
public class AmbigousMethod : AutoMapperSpecBase
1565
{
1666
public class Source

0 commit comments

Comments
 (0)