From 768aba8de78b17cec5df64c12b241ac00a0de3cf Mon Sep 17 00:00:00 2001 From: chaowlert Date: Mon, 2 Mar 2020 06:08:45 +0700 Subject: [PATCH] fix #225, #141 support mapping to IDictionary, IReadOnlyDictionary --- src/.editorconfig | 3 + .../WhenMappingWithDictionary.cs | 6 +- .../WhenMappingWithIReadOnlyDictionary.cs | 318 ++++++++++++++++++ src/Mapster/Adapters/DictionaryAdapter.cs | 73 ++-- src/Mapster/Adapters/StringAdapter.cs | 7 +- src/Mapster/Mapster.csproj | 2 +- .../Settings/ValueAccessingStrategy.cs | 12 +- src/Mapster/Utils/MapsterHelper.cs | 16 + src/Mapster/Utils/ReflectionUtils.cs | 22 +- 9 files changed, 419 insertions(+), 40 deletions(-) create mode 100644 src/Mapster.Tests/WhenMappingWithIReadOnlyDictionary.cs diff --git a/src/.editorconfig b/src/.editorconfig index 1ce25b56..3c0a9b60 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -8,3 +8,6 @@ dotnet_diagnostic.RCS1238.severity = suggestion # S3358: Ternary operators should not be nested dotnet_diagnostic.S3358.severity = suggestion + +# S125: Sections of code should not be commented out +dotnet_diagnostic.S125.severity = suggestion diff --git a/src/Mapster.Tests/WhenMappingWithDictionary.cs b/src/Mapster.Tests/WhenMappingWithDictionary.cs index 955adaa8..e76212da 100644 --- a/src/Mapster.Tests/WhenMappingWithDictionary.cs +++ b/src/Mapster.Tests/WhenMappingWithDictionary.cs @@ -1,8 +1,6 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Shouldly; @@ -63,7 +61,7 @@ public void Object_To_Dictionary_Map() public void Object_To_Dictionary_CamelCase() { var config = new TypeAdapterConfig(); - config.NewConfig>() + config.NewConfig>() .TwoWays() .NameMatchingStrategy(NameMatchingStrategy.ToCamelCase); @@ -73,7 +71,7 @@ public void Object_To_Dictionary_CamelCase() Name = "test", }; - var dict = poco.Adapt>(config); + var dict = poco.Adapt>(config); dict.Count.ShouldBe(2); dict["id"].ShouldBe(poco.Id); diff --git a/src/Mapster.Tests/WhenMappingWithIReadOnlyDictionary.cs b/src/Mapster.Tests/WhenMappingWithIReadOnlyDictionary.cs new file mode 100644 index 00000000..f00cc384 --- /dev/null +++ b/src/Mapster.Tests/WhenMappingWithIReadOnlyDictionary.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; + +namespace Mapster.Tests +{ + [TestClass] + public class WhenMappingWithIReadOnlyDictionary + { + [TestInitialize] + public void Setup() + { + TypeAdapterConfig.GlobalSettings.Clear(); + } + + [TestCleanup] + public void TestCleanup() + { + TypeAdapterConfig.GlobalSettings.Default.NameMatchingStrategy(NameMatchingStrategy.Exact); + } + + [TestMethod] + public void Object_To_Dictionary() + { + var poco = new SimplePoco + { + Id = Guid.NewGuid(), + Name = "test", + }; + + var dict = TypeAdapter.Adapt>(poco); + + dict.Count.ShouldBe(2); + dict["Id"].ShouldBe(poco.Id); + dict["Name"].ShouldBe(poco.Name); + } + + + [TestMethod] + public void Object_To_Dictionary_Map() + { + var poco = new SimplePoco + { + Id = Guid.NewGuid(), + Name = "test", + }; + + var config = new TypeAdapterConfig(); + config.NewConfig>() + .Map("Code", c => c.Id); + var dict = poco.Adapt>(config); + + dict.Count.ShouldBe(2); + dict["Code"].ShouldBe(poco.Id); + dict["Name"].ShouldBe(poco.Name); + } + + [TestMethod] + public void Object_To_Dictionary_CamelCase() + { + var config = new TypeAdapterConfig(); + config.NewConfig>() + .TwoWays() + .NameMatchingStrategy(NameMatchingStrategy.ToCamelCase); + + var poco = new SimplePoco + { + Id = Guid.NewGuid(), + Name = "test", + }; + + var dict = poco.Adapt>(config); + + dict.Count.ShouldBe(2); + dict["id"].ShouldBe(poco.Id); + dict["name"].ShouldBe(poco.Name); + + var poco2 = dict.Adapt(config); + poco2.Id.ShouldBe(dict["id"]); + poco2.Name.ShouldBe(dict["name"]); + } + + [TestMethod] + public void Object_To_Dictionary_Flexible() + { + TypeAdapterConfig.GlobalSettings.Default.NameMatchingStrategy(NameMatchingStrategy.Flexible); + var poco = new SimplePoco + { + Id = Guid.NewGuid(), + Name = "test", + }; + + IReadOnlyDictionary dict = new Dictionary + { + ["id"] = Guid.NewGuid() + }; + + TypeAdapter.Adapt(poco, dict); + + dict.Count.ShouldBe(2); + dict["id"].ShouldBe(poco.Id); + dict["Name"].ShouldBe(poco.Name); + } + + [TestMethod] + public void Object_To_Dictionary_Ignore_Null_Values() + { + TypeAdapterConfig>.NewConfig() + .IgnoreNullValues(true); + + var poco = new SimplePoco + { + Id = Guid.NewGuid(), + Name = null, + }; + + var dict = TypeAdapter.Adapt>(poco); + + dict.Count.ShouldBe(1); + dict["Id"].ShouldBe(poco.Id); + } + + [TestMethod] + public void Dictionary_To_Object() + { + var dict = new Dictionary + { + ["Id"] = Guid.NewGuid(), + ["Foo"] = "test", + }; + TypeAdapterConfig, SimplePoco>.NewConfig() + .Compile(); + + var poco = TypeAdapter.Adapt, SimplePoco>(dict); + poco.Id.ShouldBe(dict["Id"]); + poco.Name.ShouldBeNull(); + } + + + [TestMethod] + public void Dictionary_To_Object_Map() + { + var dict = new Dictionary + { + ["Code"] = Guid.NewGuid(), + ["Foo"] = "test", + }; + + TypeAdapterConfig, SimplePoco>.NewConfig() + .Map(c => c.Id, "Code") + .Compile(); + + var poco = TypeAdapter.Adapt, SimplePoco>(dict); + poco.Id.ShouldBe(dict["Code"]); + poco.Name.ShouldBeNull(); + } + + [TestMethod] + public void Dictionary_To_Object_CamelCase() + { + TypeAdapterConfig.GlobalSettings.Default.NameMatchingStrategy(NameMatchingStrategy.FromCamelCase); + TypeAdapterConfig, SimplePoco>.NewConfig() + .Compile(); + var dict = new Dictionary + { + ["id"] = Guid.NewGuid(), + ["Name"] = "bar", + ["foo"] = "test", + }; + + var poco = TypeAdapter.Adapt, SimplePoco>(dict); + poco.Id.ShouldBe(dict["id"]); + poco.Name.ShouldBeNull(); + } + + [TestMethod] + public void Dictionary_To_Object_Flexible() + { + var config = new TypeAdapterConfig(); + config.Default.NameMatchingStrategy(NameMatchingStrategy.Flexible); + var dict = new Dictionary + { + ["id"] = Guid.NewGuid(), + ["Name"] = "bar", + ["foo"] = "test", + }; + + var poco = TypeAdapter.Adapt, SimplePoco>(dict, config); + poco.Id.ShouldBe(dict["id"]); + poco.Name.ShouldBe(dict["Name"]); + } + + [TestMethod] + public void Dictionary_Of_Int() + { + var result = TypeAdapter.Adapt(new A { Prop = new Dictionary { { 1, 2m } } }); + result.Prop[1].ShouldBe(2m); + } + + [TestMethod] + public void Dictionary_Of_String() + { + IReadOnlyDictionary dict = new Dictionary + { + ["a"] = 1 + }; + var result = dict.Adapt>(); + result["a"].ShouldBe(1); + } + + [TestMethod] + public void Dictionary_Of_String_Mix() + { + TypeAdapterConfig, IReadOnlyDictionary>.NewConfig() + .Map("A", "a") + .Ignore("c") + .IgnoreIf((src, dest) => src.Count > 3, "d") + .IgnoreNullValues(true) + .NameMatchingStrategy(NameMatchingStrategy.ConvertSourceMemberName(s => "_" + s)); + IReadOnlyDictionary dict = new Dictionary + { + ["a"] = 1, + ["b"] = 2, + ["c"] = 3, + ["d"] = 4, + ["e"] = null, + }; + var result = dict.Adapt>(); + result.Count.ShouldBe(2); + result["A"].ShouldBe(1); + result["_b"].ShouldBe(2); + } + + [TestMethod] + public void AdaptClassWithIntegerKeyDictionary() + { + var instanceWithDictionary = new ClassWithIntKeyDictionary(); + instanceWithDictionary.Dict = new Dictionary + { + { 1 , new SimplePoco { Id = Guid.NewGuid(), Name = "one"} }, + { 100 , new SimplePoco { Id = Guid.NewGuid(), Name = "one hundred"} }, + }; + + var result = instanceWithDictionary.Adapt(); + result.Dict[1].Name.ShouldBe("one"); + result.Dict[100].Name.ShouldBe("one hundred"); + } + + [TestMethod] + public void AdaptClassWithIntegerKeyDictionaryInterface() + { + var instanceWithDictionary = new ClassWithIntKeyDictionary(); + instanceWithDictionary.Dict = new Dictionary + { + { 1 , new SimplePoco { Id = Guid.NewGuid(), Name = "one"} }, + { 100 , new SimplePoco { Id = Guid.NewGuid(), Name = "one hundred"} }, + }; + + var result = instanceWithDictionary.Adapt(); + result.Dict[1].Name.ShouldBe("one"); + result.Dict[100].Name.ShouldBe("one hundred"); + result.Dict.ShouldNotBeSameAs(instanceWithDictionary.Dict); + } + + [TestMethod] + public void AdaptClassWithObjectKeyDictionary() + { + var instanceWithDictionary = new ClassWithPocoKeyDictionary(); + instanceWithDictionary.Dict = new Dictionary + { + { new SimplePoco { Id = Guid.NewGuid(), Name = "one"}, 1 }, + { new SimplePoco { Id = Guid.NewGuid(), Name = "one hundred"}, 100 }, + }; + + var result = instanceWithDictionary.Adapt(); + result.Dict.Keys.Any(k => k.Name == "one").ShouldBeTrue(); + result.Dict.Keys.Any(k => k.Name == "one hundred").ShouldBeTrue(); + } + + public class SimplePoco + { + public Guid Id { get; set; } + public string Name { get; set; } + } + + public class A + { + public IReadOnlyDictionary Prop { get; set; } + } + + public class ClassWithIntKeyDictionary + { + public IReadOnlyDictionary Dict { get; set; } + } + + public class ClassWithIntKeyIDictionary + { + public IReadOnlyDictionary Dict { get; set; } + } + + public class OtherClassWithIntKeyDictionary + { + public IReadOnlyDictionary Dict { get; set; } + } + + public class ClassWithPocoKeyDictionary + { + public IReadOnlyDictionary Dict { get; set; } + } + + public class OtherClassWithPocoKeyDictionary + { + public IReadOnlyDictionary Dict { get; set; } + } + } +} diff --git a/src/Mapster/Adapters/DictionaryAdapter.cs b/src/Mapster/Adapters/DictionaryAdapter.cs index 787b79ef..6df84a0a 100644 --- a/src/Mapster/Adapters/DictionaryAdapter.cs +++ b/src/Mapster/Adapters/DictionaryAdapter.cs @@ -30,14 +30,45 @@ protected override bool CanInline(Expression source, Expression? destination, Co || arg.Settings.IgnoreNonMapped == true; } + protected override Expression CreateInstantiationExpression(Expression source, Expression? destination, CompileArgument arg) + { + if (arg.DestinationType.GetTypeInfo().IsInterface) + { + var dict = arg.DestinationType.GetDictionaryType()!; + var dictArgs = dict.GetGenericArguments(); + var dictType = typeof(Dictionary<,>).MakeGenericType(dictArgs); + return Expression.New(dictType); + } + return base.CreateInstantiationExpression(source, destination, arg); + } + protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg) { - var mapped = base.CreateBlockExpression(source, destination, arg); + var dictArgs = destination.Type.GetDictionaryType()!.GetGenericArguments(); + var shouldConvert = destination.Type.GetMethod("Add", dictArgs) == null; + + //var dict = (IDictionary<,>)dest; + var actions = new List(); + var dict = destination; + if (shouldConvert) + { + var dictType = typeof(IDictionary<,>).MakeGenericType(dictArgs); + dict = Expression.Variable(dictType, "dict"); + actions.Add(ExpressionEx.Assign(dict, destination)); //convert to dict type + } + + var mapped = base.CreateBlockExpression(source, dict, arg); + if (mapped.NodeType != ExpressionType.Default) + actions.Add(mapped); - //if source is not dict type, use ClassAdapter + //if source is not dict type, use ClassAdapter only var srcDictType = arg.SourceType.GetDictionaryType(); if (srcDictType == null || arg.Settings.IgnoreNonMapped == true) - return mapped; + { + return shouldConvert && mapped.NodeType != ExpressionType.Default + ? Expression.Block(new[] {(ParameterExpression)dict}, actions) + : mapped; + } var keyType = srcDictType.GetGenericArguments().First(); var kvpType = source.Type.ExtractCollectionType(); @@ -46,7 +77,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio var keyAssign = Expression.Assign(key, Expression.Property(kvp, "Key")); //dest[kvp.Key] = convert(kvp.Value); - var set = CreateSetFromKvp(kvp, key, destination, arg); + var set = CreateSetFromKvp(kvp, key, dict, arg); if (arg.Settings.NameMatchingStrategy.SourceMemberNameConverter != NameMatchingStrategy.Identity) { set = Expression.Block( @@ -67,7 +98,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio .ToHashSet(); //ignore - var dict = new Dictionary(); + var ignoreIfs = new Dictionary(); foreach (var ignore in arg.Settings.Ignore) { if (ignore.Value.Condition == null) @@ -76,18 +107,18 @@ protected override Expression CreateBlockExpression(Expression source, Expressio { var body = ignore.Value.IsChildPath ? ignore.Value.Condition.Body - : ignore.Value.Condition.Apply(arg.MapType, source, destination); + : ignore.Value.Condition.Apply(arg.MapType, source, dict); var setWithCondition = Expression.IfThen( ExpressionEx.Not(body), set); - dict.Add(ignore.Key, setWithCondition); + ignoreIfs.Add(ignore.Key, setWithCondition); } } //dict to switch - if (dict.Count > 0 || ignores.Count > 0) + if (ignoreIfs.Count > 0 || ignores.Count > 0) { - var cases = dict + var cases = ignoreIfs .Select(k => Expression.SwitchCase(k.Value, Expression.Constant(k.Key))) .ToList(); if (ignores.Count > 0) @@ -113,16 +144,17 @@ protected override Expression CreateBlockExpression(Expression source, Expressio //} set = Expression.Block(new[] { key }, keyAssign, set); var loop = ExpressionEx.ForEach(source, kvp, set); - return mapped.NodeType == ExpressionType.Default - ? loop - : Expression.Block(mapped, loop); + actions.Add(loop); + return shouldConvert + ? Expression.Block(new[] {(ParameterExpression)dict}, actions) + : Expression.Block(actions); } private Expression CreateSetFromKvp(Expression kvp, Expression key, Expression destination, CompileArgument arg) { var kvpValue = Expression.Property(kvp, "Value"); - var destDictType = arg.DestinationType.GetDictionaryType(); + var destDictType = destination.Type.GetDictionaryType()!; var destValueType = destDictType.GetGenericArguments()[1]; var destGetFn = GetFunction(arg, destDictType); var destSetFn = SetFunction(arg, destDictType); @@ -148,7 +180,7 @@ protected override Expression CreateInlineExpression(Expression source, CompileA var classConverter = CreateClassConverter(source, classModel, arg); var members = classConverter.Members; - var dictType = arg.DestinationType.GetDictionaryType(); + var dictType = exp.Type.GetDictionaryType()!; var keyType = dictType.GetGenericArguments()[0]; var valueType = dictType.GetGenericArguments()[1]; var add = dictType.GetMethod("Add", new[] { keyType, valueType }); @@ -186,14 +218,14 @@ protected override ClassModel GetSetterModel(CompileArgument arg) } //create model - var dictType = arg.DestinationType.GetDictionaryType(); - var valueType = dictType.GetGenericArguments()[1]; + var dictArgs = arg.DestinationType.GetDictionaryType()!.GetGenericArguments(); + var dictType = typeof(IDictionary<,>).MakeGenericType(dictArgs); var getFn = GetFunction(arg, dictType); var setFn = SetFunction(arg, dictType); var sourceModels = destNames - .Select(name => new KeyValuePairModel(name, valueType, getFn, setFn)) + .Select(name => new KeyValuePairModel(name, dictArgs[1], getFn, setFn)) .ToList(); return new ClassModel @@ -205,20 +237,19 @@ protected override ClassModel GetSetterModel(CompileArgument arg) private static Func GetFunction(CompileArgument arg, Type dictType) { var strategy = arg.Settings.NameMatchingStrategy; + var args = dictType.GetGenericArguments(); if (strategy.DestinationMemberNameConverter != NameMatchingStrategy.Identity) { - var args = dictType.GetGenericArguments(); var getMethod = typeof(MapsterHelper).GetMethods() - .First(m => m.Name == nameof(MapsterHelper.FlexibleGet)) + .First(m => m.Name == nameof(MapsterHelper.FlexibleGet) && m.GetParameters()[0].ParameterType.Name == dictType.Name) .MakeGenericMethod(args[1]); var destNameConverter = MapsterHelper.GetConverterExpression(strategy.DestinationMemberNameConverter); return (dict, key) => Expression.Call(getMethod, dict, key, destNameConverter); } else { - var args = dictType.GetGenericArguments(); var getMethod = typeof(MapsterHelper).GetMethods() - .First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault)) + .First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault) && m.GetParameters()[0].ParameterType.Name == dictType.Name) .MakeGenericMethod(args); return (dict, key) => Expression.Call(getMethod, dict, key); } diff --git a/src/Mapster/Adapters/StringAdapter.cs b/src/Mapster/Adapters/StringAdapter.cs index 1d5f9fb4..24cf8443 100644 --- a/src/Mapster/Adapters/StringAdapter.cs +++ b/src/Mapster/Adapters/StringAdapter.cs @@ -17,11 +17,8 @@ protected override Expression ConvertType(Expression source, Type destinationTyp { if (destinationType == typeof(string)) { - var method = source.Type.GetMethod("ToString", Type.EmptyTypes); - if (method == null) - { - method = typeof(object).GetMethod("ToString", Type.EmptyTypes); - } + var method = source.Type.GetMethod("ToString", Type.EmptyTypes) + ?? typeof(object).GetMethod("ToString", Type.EmptyTypes); return Expression.Call(source, method); } else //if (sourceType == typeof(string)) diff --git a/src/Mapster/Mapster.csproj b/src/Mapster/Mapster.csproj index 307dbe84..58a22099 100644 --- a/src/Mapster/Mapster.csproj +++ b/src/Mapster/Mapster.csproj @@ -19,7 +19,7 @@ 1.6.1 True Mapster - 5.0.0 + 5.1.0 8.0 enable diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index e200c0b9..d937f48b 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -188,12 +188,16 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM var args = dictType.GetGenericArguments(); if (strategy.SourceMemberNameConverter != NameMatchingStrategy.Identity) { - var method = typeof(MapsterHelper).GetMethods().First(m => m.Name == nameof(MapsterHelper.FlexibleGet)).MakeGenericMethod(args[1]); + var method = typeof(MapsterHelper).GetMethods() + .First(m => m.Name == nameof(MapsterHelper.FlexibleGet) && m.GetParameters()[0].ParameterType.Name == dictType.Name) + .MakeGenericMethod(args[1]); return Expression.Call(method, source.To(dictType), key, MapsterHelper.GetConverterExpression(strategy.SourceMemberNameConverter)); } else { - var method = typeof(MapsterHelper).GetMethods().First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault)).MakeGenericMethod(args); + var method = typeof(MapsterHelper).GetMethods() + .First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault) && m.GetParameters()[0].ParameterType.Name == dictType.Name) + .MakeGenericMethod(args); return Expression.Call(method, source.To(dictType), key); } } @@ -208,7 +212,9 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM if (dictType == null) return null; var args = dictType.GetGenericArguments(); - var method = typeof(MapsterHelper).GetMethods().First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault)).MakeGenericMethod(args); + var method = typeof(MapsterHelper).GetMethods() + .First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault) && m.GetParameters()[0].ParameterType.Name == dictType.Name) + .MakeGenericMethod(args); Expression? getter = null; Expression? lastCondition = null; diff --git a/src/Mapster/Utils/MapsterHelper.cs b/src/Mapster/Utils/MapsterHelper.cs index 5044fc70..91a3328b 100644 --- a/src/Mapster/Utils/MapsterHelper.cs +++ b/src/Mapster/Utils/MapsterHelper.cs @@ -12,6 +12,13 @@ public static class MapsterHelper return dict.TryGetValue(key, out var value) ? value : default!; } +#if !NET40 + public static U GetValueOrDefault(IReadOnlyDictionary dict, T key) + { + return dict.TryGetValue(key, out var value) ? value : default!; + } +#endif + public static U FlexibleGet(IDictionary dict, string key, Func keyConverter) { return (from kvp in dict @@ -19,6 +26,15 @@ public static U FlexibleGet(IDictionary dict, string key, Func(IReadOnlyDictionary dict, string key, Func keyConverter) + { + return (from kvp in dict + where keyConverter(kvp.Key) == key + select kvp.Value).FirstOrDefault(); + } +#endif + public static void FlexibleSet(IDictionary dict, string key, Func keyConverter, U value) { var dictKey = (from kvp in dict diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index 0dac95f8..5e7d1211 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -152,7 +152,7 @@ public static bool IsGenericEnumerableType(this Type type) return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>); } - public static Type GetInterface(this Type type, Predicate predicate) + public static Type? GetInterface(this Type type, Predicate predicate) { if (predicate(type)) return type; @@ -160,7 +160,7 @@ public static Type GetInterface(this Type type, Predicate predicate) return Array.Find(type.GetInterfaces(), predicate); } - public static Type GetGenericEnumerableType(this Type type) + public static Type? GetGenericEnumerableType(this Type type) { return type.GetInterface(IsGenericEnumerableType); } @@ -263,15 +263,25 @@ public static bool IsListCompatible(this Type type) return false; } - public static Type GetDictionaryType(this Type destinationType) + public static Type? GetDictionaryType(this Type destinationType) { - return destinationType.GetInterface(type => type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>)); +#if !NET40 + if (destinationType.GetTypeInfo().IsInterface + && destinationType.GetTypeInfo().IsGenericType + && destinationType.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)) + { + return destinationType; + } +#endif + return destinationType.GetInterface(type => + type.GetTypeInfo().IsGenericType && + type.GetGenericTypeDefinition() == typeof(IDictionary<,>) ); } public static AccessModifier GetAccessModifier(this FieldInfo memberInfo) { if (memberInfo.IsFamilyOrAssembly) - return AccessModifier.Protected | AccessModifier.Internal; + return AccessModifier.ProtectedInternal; if (memberInfo.IsFamily) return AccessModifier.Protected; if (memberInfo.IsAssembly) @@ -284,7 +294,7 @@ public static AccessModifier GetAccessModifier(this FieldInfo memberInfo) public static AccessModifier GetAccessModifier(this MethodBase methodBase) { if (methodBase.IsFamilyOrAssembly) - return AccessModifier.Protected | AccessModifier.Internal; + return AccessModifier.ProtectedInternal; if (methodBase.IsFamily) return AccessModifier.Protected; if (methodBase.IsAssembly)