Skip to content

Commit 8f8794e

Browse files
authored
Merge pull request #2873 from AutoMapper/read-only-dictionaries
Read only dictionaries support
2 parents 7ac7f03 + 681fc16 commit 8f8794e

File tree

9 files changed

+413
-128
lines changed

9 files changed

+413
-128
lines changed

src/AutoMapper/Configuration/Internal/PrimitiveHelper.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public static bool IsListOrDictionaryType(Type type)
4949
public static bool IsDictionaryType(Type type)
5050
=> type.ImplementsGenericInterface(typeof(IDictionary<,>));
5151

52+
public static bool IsReadOnlyDictionaryType(Type type)
53+
=> type.ImplementsGenericInterface(typeof(IReadOnlyDictionary<,>));
54+
5255
public static bool ImplementsGenericInterface(Type type, Type interfaceType)
5356
{
5457
return type.IsGenericType(interfaceType)
@@ -64,6 +67,9 @@ public static Type GetIEnumerableType(Type type)
6467
public static Type GetDictionaryType(Type type)
6568
=> type.GetGenericInterface(typeof(IDictionary<,>));
6669

70+
public static Type GetReadOnlyDictionaryType(Type type)
71+
=> type.GetGenericInterface(typeof(IReadOnlyDictionary<,>));
72+
6773
public static Type GetGenericInterface(Type type, Type genericInterface)
6874
{
6975
return type.IsGenericType(genericInterface)

src/AutoMapper/Configuration/PrimitiveExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ public static bool IsListOrDictionaryType(this Type type)
4343
public static bool IsDictionaryType(this Type type)
4444
=> PrimitiveHelper.IsDictionaryType(type);
4545

46+
public static bool IsReadOnlyDictionaryType(this Type type)
47+
=> PrimitiveHelper.IsReadOnlyDictionaryType(type);
48+
4649
public static bool ImplementsGenericInterface(this Type type, Type interfaceType)
4750
=> PrimitiveHelper.ImplementsGenericInterface(type, interfaceType);
4851

@@ -55,6 +58,9 @@ public static Type GetIEnumerableType(this Type type)
5558
public static Type GetDictionaryType(this Type type)
5659
=> PrimitiveHelper.GetDictionaryType(type);
5760

61+
public static Type GetReadOnlyDictionaryType(this Type type)
62+
=> PrimitiveHelper.GetReadOnlyDictionaryType(type);
63+
5864
public static Type GetGenericInterface(this Type type, Type genericInterface)
5965
=> PrimitiveHelper.GetGenericInterface(type, genericInterface);
6066

src/AutoMapper/Execution/DelegateFactory.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
34
using System.Linq;
45
using System.Linq.Expressions;
56
using AutoMapper.Configuration;
@@ -50,14 +51,12 @@ public static Expression GenerateConstructorExpression(Type type)
5051

5152
if (type.IsInterface())
5253
{
53-
return
54-
type.IsDictionaryType() ?
55-
CreateCollection(type, typeof(Dictionary<,>))
56-
: type.IsSetType() ?
57-
CreateCollection(type, typeof(HashSet<>))
58-
: type.IsEnumerableType() ?
59-
CreateCollection(type, typeof(List<>))
60-
: InvalidType(type, $"Cannot create an instance of interface type {type}.");
54+
return
55+
type.IsDictionaryType() ? CreateCollection(type, typeof(Dictionary<,>))
56+
: type.IsReadOnlyDictionaryType() ? CreateReadOnlyCollection(type, typeof(ReadOnlyDictionary<,>))
57+
: type.IsSetType() ? CreateCollection(type, typeof(HashSet<>))
58+
: type.IsEnumerableType() ? CreateCollection(type, typeof(List<>))
59+
: InvalidType(type, $"Cannot create an instance of interface type {type}.");
6160
}
6261

6362
if (type.IsAbstract())
@@ -93,6 +92,17 @@ private static Expression CreateCollection(Type type, Type collectionType)
9392
return InvalidType(type, $"Cannot create an instance of interface type {type}.");
9493
}
9594

95+
private static Expression CreateReadOnlyCollection(Type type, Type collectionType)
96+
{
97+
var listType = collectionType.MakeGenericType(GetElementTypes(type, ElementTypeFlags.BreakKeyValuePair));
98+
var ctor = listType.GetConstructors()[0];
99+
var innerType = ctor.GetParameters()[0].ParameterType;
100+
if (type.IsAssignableFrom(listType))
101+
return ToType(New(ctor, GenerateConstructorExpression(innerType)), type);
102+
103+
return InvalidType(type, $"Cannot create an instance of interface type {type}.");
104+
}
105+
96106
private static Expression InvalidType(Type type, string message)
97107
{
98108
var ex = new ArgumentException(message, "type");

src/AutoMapper/Mappers/Internal/ElementTypeHelper.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,22 @@ public static Type[] GetElementTypes(Type enumerableType, IEnumerable enumerable
2424
return new[] {enumerableType.GetElementType()};
2525
}
2626

27-
var idictionaryType = enumerableType.GetDictionaryType();
28-
if (idictionaryType != null && flags.HasFlag(ElementTypeFlags.BreakKeyValuePair))
27+
var iDictionaryType = enumerableType.GetDictionaryType();
28+
if (iDictionaryType != null && flags.HasFlag(ElementTypeFlags.BreakKeyValuePair))
2929
{
30-
return idictionaryType.GetTypeInfo().GenericTypeArguments;
30+
return iDictionaryType.GetTypeInfo().GenericTypeArguments;
3131
}
3232

33-
var ienumerableType = enumerableType.GetIEnumerableType();
34-
if (ienumerableType != null)
33+
var iReadOnlyDictionaryType = enumerableType.GetReadOnlyDictionaryType();
34+
if (iReadOnlyDictionaryType != null && flags.HasFlag(ElementTypeFlags.BreakKeyValuePair))
3535
{
36-
return ienumerableType.GetTypeInfo().GenericTypeArguments;
36+
return iReadOnlyDictionaryType.GetTypeInfo().GenericTypeArguments;
37+
}
38+
39+
var iEnumerableType = enumerableType.GetIEnumerableType();
40+
if (iEnumerableType != null)
41+
{
42+
return iEnumerableType.GetTypeInfo().GenericTypeArguments;
3743
}
3844

3945
if (typeof(IEnumerable).IsAssignableFrom(enumerableType))

src/AutoMapper/Mappers/MapperRegistry.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal static class MapperRegistry
1919
new ArrayMapper(),
2020
new EnumerableToDictionaryMapper(),
2121
new NameValueCollectionMapper(),
22+
new ReadOnlyDictionaryMapper(),
2223
new DictionaryMapper(),
2324
new ReadOnlyCollectionMapper(),
2425
new HashSetMapper(),

src/AutoMapper/Mappers/ReadOnlyCollectionMapper.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ public Expression MapExpression(IConfigurationProvider configurationProvider, Pr
2929
var list = MapCollectionExpression(configurationProvider, profileMap, memberMap, sourceExpression, Default(listType), contextExpression, typeof(List<>), MapItemExpr);
3030
var dest = Variable(listType, "dest");
3131

32-
return Block(new[] { dest }, Assign(dest, list), Condition(NotEqual(dest, Default(listType)), New(destExpression.Type.GetDeclaredConstructors().First(), dest), Default(destExpression.Type)));
32+
var ctor = destExpression.Type.GetDeclaredConstructors()
33+
.First(ci => ci.GetParameters().Length == 1 && ci.GetParameters()[0].ParameterType.IsAssignableFrom(dest.Type));
34+
35+
return Block(new[] { dest },
36+
Assign(dest, list),
37+
Condition(NotEqual(dest, Default(listType)),
38+
New(ctor, dest),
39+
Default(destExpression.Type)));
3340
}
3441
}
3542
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Collections.Generic;
2+
using System.Collections.ObjectModel;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using AutoMapper.Configuration;
6+
using AutoMapper.Internal;
7+
using AutoMapper.Mappers.Internal;
8+
9+
namespace AutoMapper.Mappers
10+
{
11+
using static Expression;
12+
using static ExpressionFactory;
13+
using static CollectionMapperExpressionFactory;
14+
15+
public class ReadOnlyDictionaryMapper : IObjectMapper
16+
{
17+
public bool IsMatch(TypePair context)
18+
{
19+
if (!(context.SourceType.IsEnumerableType() && context.DestinationType.IsGenericType()))
20+
return false;
21+
22+
var genericType = context.DestinationType.GetGenericTypeDefinition();
23+
24+
return genericType == typeof(ReadOnlyDictionary<,>) || genericType == typeof(IReadOnlyDictionary<,>);
25+
}
26+
27+
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
28+
IMemberMap memberMap, Expression sourceExpression, Expression destExpression, Expression contextExpression)
29+
{
30+
var dictionaryTypes = ElementTypeHelper.GetElementTypes(destExpression.Type, ElementTypeFlags.BreakKeyValuePair);
31+
var dictType = typeof(Dictionary<,>).MakeGenericType(dictionaryTypes);
32+
var dict = MapCollectionExpression(configurationProvider, profileMap, memberMap, sourceExpression, Default(dictType), contextExpression, typeof(Dictionary<,>), MapKeyPairValueExpr);
33+
var dest = Variable(dictType, "dest");
34+
35+
var readOnlyDictType = destExpression.Type.IsInterface
36+
? typeof(ReadOnlyDictionary<,>).MakeGenericType(dictionaryTypes)
37+
: destExpression.Type;
38+
39+
var ctor = readOnlyDictType.GetDeclaredConstructors()
40+
.First(ci => ci.GetParameters().Length == 1 && ci.GetParameters()[0].ParameterType.IsAssignableFrom(dest.Type));
41+
42+
return Block(new[] { dest },
43+
Assign(dest, dict),
44+
Condition(NotEqual(dest, Default(dictType)),
45+
ToType(New(ctor, dest), destExpression.Type),
46+
Default(destExpression.Type)));
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)