From 63d678ed79b143787a11b51285c751b9563c8841 Mon Sep 17 00:00:00 2001 From: chaowlert Date: Wed, 15 Jan 2020 19:45:21 +0700 Subject: [PATCH 1/7] Update README.md --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b7499d7f..f9b1b3e6 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,16 @@ using (MyDbContext context = new MyDbContext()) - Map to constructor ### Why Mapster? -#### Performance -Don't let other libraries slow you down, Mapster performance is at least 2.5 times faster! -And 3.x times faster with [FastExpressionCompiler](https://github.com/MapsterMapper/Mapster/wiki/FastExpressionCompiler) and [Mapster CodeGen](https://github.com/MapsterMapper/Mapster/wiki/CodeGen)! - -![image](https://user-images.githubusercontent.com/5763993/46615061-8cc78980-cb41-11e8-8bea-b04d9fcabccd.png) +#### Performance & Memory efficient +Mapster was designed to be efficient on both speed and memory. You could gain 5x faster while using only 1/3 of memory. +And you could gain 13x faster with [Mapster CodeGen](https://github.com/MapsterMapper/Mapster/wiki/CodeGen)! + +| Method | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | +|-------------------------- |---------------:|-------------:|-------------:|------------:|------:|------:|-----------:| +| 'Mapster 4.1.1' | 1,388,744.4 us | 3,987.51 us | 6,028.54 us | 312000.0000 | - | - | 1251.22 MB | +| 'Mapster 4.1.1 (Codegen)' | 505,727.6 us | 2,525.21 us | 3,817.75 us | 312000.0000 | - | - | 1251.22 MB | +| 'ExpressMapper 1.9.1' | 3,122,200.4 us | 11,701.40 us | 19,663.45 us | 604000.0000 | - | - | 2418.52 MB | +| 'AutoMapper 9.0.0' | 6,883,546.7 us | 28,159.65 us | 42,573.37 us | 911000.0000 | - | - | 3646.85 MB | #### Step into debugging From f2b2a6e187b67802a1ded313d36c31eeea3ac64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stano=20Pe=C5=A5ko?= Date: Thu, 16 Jan 2020 23:42:33 +0100 Subject: [PATCH 2/7] Implement map to (simple) interface --- src/Mapster.Tests/WhenMappingToInterface.cs | 127 ++++++++++++++++++++ src/Mapster/Adapters/BaseAdapter.cs | 10 +- src/Mapster/Mapster.csproj | 5 +- src/Mapster/Utils/DynamicTypeGenerator.cs | 104 ++++++++++++++++ 4 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 src/Mapster.Tests/WhenMappingToInterface.cs create mode 100644 src/Mapster/Utils/DynamicTypeGenerator.cs diff --git a/src/Mapster.Tests/WhenMappingToInterface.cs b/src/Mapster.Tests/WhenMappingToInterface.cs new file mode 100644 index 00000000..1a204a7a --- /dev/null +++ b/src/Mapster.Tests/WhenMappingToInterface.cs @@ -0,0 +1,127 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; +using System; + +namespace Mapster.Tests +{ + [TestClass] + public class WhenMappingToInterface + { + [TestInitialize] + public void Setup() + { + TypeAdapterConfig.GlobalSettings.Clear(); + } + + [TestMethod] + public void MapToInterface() + { + var dto = new Dto + { + Id = 1, + Name = "Test", + UnmappedSource = "Lorem ipsum" + }; + + IDto idto = dto.Adapt(); + + idto.ShouldNotBeNull(); + idto.Id.ShouldBe(dto.Id); + idto.Name.ShouldBe(dto.Name); + idto.UnmappedTarget.ShouldBeNull(); + } + + [TestMethod] + public void MapToInheritedInterface() + { + var dto = new InheritedDto + { + Id = 1, + Name = "Test", + DateOfBirth = new DateTime(1978, 12, 10), + UnmappedSource = "Lorem ipsum" + }; + + IInheritedDto idto = dto.Adapt(); + + idto.ShouldNotBeNull(); + idto.Id.ShouldBe(dto.Id); + idto.Name.ShouldBe(dto.Name); + idto.DateOfBirth.ShouldBe(dto.DateOfBirth); + idto.UnmappedTarget.ShouldBeNull(); + } + + [TestMethod] + public void MapToInterfaceWithMethods() + { + var dto = new Dto + { + Id = 1, + Name = "Test", + UnmappedSource = "Lorem ipsum" + }; + + IInterfaceWithMethods idto = dto.Adapt(); + + idto.ShouldNotBeNull(); + idto.Id.ShouldBe(dto.Id); + idto.Name.ShouldBe(dto.Name); + Should.Throw(() => idto.DoSomething()); + } + + [TestMethod] + public void MapToNotVisibleInterfaceThrows() + { + var dto = new Dto + { + Id = 1, + Name = "Test", + UnmappedSource = "Lorem ipsum" + }; + + var ex = Should.Throw(() => dto.Adapt()); + ex.InnerException.ShouldBeOfType(); + ex.InnerException.Message.ShouldContain("not accessible", "Correct InvalidOperationException must be thrown."); + } + + private interface INotVisibleInterface + { + int Id { get; set; } + string Name { get; set; } + } + + public interface IInterfaceWithMethods + { + int Id { get; set; } + string Name { get; set; } + void DoSomething(); + } + + public interface IDto + { + int Id { get; set; } + string Name { get; set; } + string UnmappedTarget { get; set; } + } + + public interface IInheritedDto : IDto + { + DateTime DateOfBirth { get; set; } + } + + public class Dto + { + public int Id { get; set; } + public string Name { get; set; } + public string UnmappedSource { get; set; } + } + + public class InheritedDto + { + public int Id { get; set; } + public string Name { get; set; } + public DateTime DateOfBirth { get; set; } + public string UnmappedSource { get; set; } + } + } +} diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index 71c349c2..bbab0f03 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -183,7 +183,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de { var compareNull = Expression.Equal(source, Expression.Constant(null, source.Type)); blocks.Add( - Expression.IfThen(compareNull, + Expression.IfThen(compareNull, Expression.Return(label, arg.DestinationType.CreateDefault())) ); } @@ -250,7 +250,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de assignActions.Add(refAssign); var usingBody = Expression.Block( - new[] { cache, references, result }, + new[] { cache, references, result }, new Expression[] {assignReferences, setResult} .Concat(assignActions) .Concat(settingActions)); @@ -332,6 +332,12 @@ protected virtual Expression CreateInstantiationExpression(Expression source, Ex arg.DestinationType); } + //if mapping to interface, create dynamic type implementing it + else if (destination is null && arg.DestinationType.IsInterface) + { + return Expression.New(DynamicTypeGenerator.GetTypeForInterface(arg.DestinationType)); + } + //otherwise throw else { diff --git a/src/Mapster/Mapster.csproj b/src/Mapster/Mapster.csproj index 92539f9e..f242bbe0 100644 --- a/src/Mapster/Mapster.csproj +++ b/src/Mapster/Mapster.csproj @@ -4,7 +4,7 @@ A fast, fun and stimulating object to object mapper. Kind of like AutoMapper, just simpler and way, way faster. Copyright (c) 2016 Chaowlert Chaisrichalermpol, Eric Swann chaowlert;eric_swann - netstandard2.0;netstandard1.3;net45;net40 + netstandard2.0;net45;net40 Mapster Mapster.snk true @@ -27,15 +27,18 @@ + + + \ No newline at end of file diff --git a/src/Mapster/Utils/DynamicTypeGenerator.cs b/src/Mapster/Utils/DynamicTypeGenerator.cs new file mode 100644 index 00000000..ef8b19c9 --- /dev/null +++ b/src/Mapster/Utils/DynamicTypeGenerator.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Concurrent; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading; + +namespace Mapster.Utils +{ + internal static class DynamicTypeGenerator + { + private const string DynamicAssemblyName = "MapsterGeneratedTypes"; + + private static readonly AssemblyBuilder _assemblyBuilder = +#if NET40 + AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(DynamicAssemblyName), AssemblyBuilderAccess.Run); +#else + AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(DynamicAssemblyName), AssemblyBuilderAccess.Run); +#endif + private static readonly ModuleBuilder _moduleBuilder = _assemblyBuilder.DefineDynamicModule("Classes"); + private static readonly ConcurrentDictionary _generated = new ConcurrentDictionary(); + private static int _generatedCounter = 0; + + public static Type GetTypeForInterface(Type interfaceType) + { + CheckInterfaceType(interfaceType); + return _generated.GetOrAdd(interfaceType, (key) => CreateTypeForInterface(key)); + } + + private static void CheckInterfaceType(Type interfaceType) + { + if (!interfaceType.IsInterface) + { + const string msg = "Cannot create dynamic type for {0}, because it is not an interface.\n" + + "Target type full name: {1}"; + throw new InvalidOperationException(string.Format(msg, interfaceType.Name, interfaceType.FullName)); + } + if (!interfaceType.IsVisible) + { + const string msg = "Cannot adapt to interface {0}, because it is not accessible outside its assembly.\n" + + "Interface full name: {1}"; + throw new InvalidOperationException(string.Format(msg, interfaceType.Name, interfaceType.FullName)); + } + } + + private static Type CreateTypeForInterface(Type interfaceType) + { + TypeBuilder builder = _moduleBuilder.DefineType("GeneratedType_" + Interlocked.Increment(ref _generatedCounter)); + builder.AddInterfaceImplementation(interfaceType); + + foreach (PropertyInfo prop in interfaceType.GetProperties()) + { + CreateProperty(interfaceType, builder, prop); + } + +#if NETSTANDARD2_0 + return builder.CreateTypeInfo(); +#else + return builder.CreateType(); +#endif + } + + private static void CreateProperty(Type interfaceType, TypeBuilder builder, PropertyInfo prop) + { + const BindingFlags interfacePropMethodsFlags = BindingFlags.Instance | BindingFlags.Public; + // The property set and get methods require a special set of attributes. + const MethodAttributes classPropMethodAttrs + = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig; + + FieldBuilder propField = builder.DefineField("_" + prop.Name, prop.PropertyType, FieldAttributes.Private); + PropertyBuilder propBuilder = builder.DefineProperty(prop.Name, PropertyAttributes.None, prop.PropertyType, null); + + if (prop.CanRead) + { + // Define the "get" accessor method for property. + string getMethodName = "get_" + prop.Name; + MethodBuilder propGet = builder.DefineMethod(getMethodName, classPropMethodAttrs, prop.PropertyType, null); + ILGenerator propGetIL = propGet.GetILGenerator(); + propGetIL.Emit(OpCodes.Ldarg_0); + propGetIL.Emit(OpCodes.Ldfld, propField); + propGetIL.Emit(OpCodes.Ret); + + MethodInfo interfaceGetMethod = interfaceType.GetMethod(getMethodName, interfacePropMethodsFlags); + builder.DefineMethodOverride(propGet, interfaceGetMethod); + propBuilder.SetGetMethod(propGet); + } + + if (prop.CanWrite) + { + // Define the "set" accessor method for property. + string setMethodName = "set_" + prop.Name; + MethodBuilder propSet = builder.DefineMethod(setMethodName, classPropMethodAttrs, null, new Type[] { prop.PropertyType }); + ILGenerator propSetIL = propSet.GetILGenerator(); + propSetIL.Emit(OpCodes.Ldarg_0); + propSetIL.Emit(OpCodes.Ldarg_1); + propSetIL.Emit(OpCodes.Stfld, propField); + propSetIL.Emit(OpCodes.Ret); + + MethodInfo interfaceSetMethod = interfaceType.GetMethod(setMethodName, interfacePropMethodsFlags); + builder.DefineMethodOverride(propSet, interfaceSetMethod); + propBuilder.SetSetMethod(propSet); + } + } + } +} From f58c714cb3136cc2f83be2e016fb96248418e1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stano=20Pe=C5=A5ko?= Date: Sat, 18 Jan 2020 01:27:19 +0100 Subject: [PATCH 3/7] Add tests for DynamicTypeGenerator --- .../DynamicTypeGeneratorTests.cs | 114 ++++++++++++++++++ src/Mapster/Utils/DynamicTypeGenerator.cs | 73 ++++++++++- 2 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 src/Mapster.Tests/DynamicTypeGeneratorTests.cs diff --git a/src/Mapster.Tests/DynamicTypeGeneratorTests.cs b/src/Mapster.Tests/DynamicTypeGeneratorTests.cs new file mode 100644 index 00000000..7477e9b3 --- /dev/null +++ b/src/Mapster.Tests/DynamicTypeGeneratorTests.cs @@ -0,0 +1,114 @@ +using Mapster.Utils; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; +using System; + +namespace Mapster.Tests +{ + [TestClass] + public class DynamicTypeGeneratorTests + { + + private interface INotVisibleInterface + { + int Id { get; set; } + string Name { get; set; } + } + + public interface ISimpleInterface + { + int Id { get; set; } + string Name { get; set; } + } + + public interface IInheritedInterface : ISimpleInterface + { + int Value { get; set; } + } + + public interface IComplexBaseInterface + { + int Id { get; set; } + void BaseMethod(); + } + + public interface IComplexInterface : IComplexBaseInterface + { + string Name { get; set; } + int ReadOnlyProp { get; } + int WriteOnlyProp { set; } + void SimpleMethod(); + int ComplexMethod(byte b, ref int i, out string s); + } + + [TestMethod] + public void ThrowExceptionWhenInterfaceIsNotVisible() + { + void action() => DynamicTypeGenerator.GetTypeForInterface(typeof(INotVisibleInterface)); + + var ex = Should.Throw(action); + ex.Message.ShouldContain("not accessible"); + } + + [TestMethod] + public void CreateTypeForSimpleInterface() + { + Type iClass = DynamicTypeGenerator.GetTypeForInterface(typeof(ISimpleInterface)); + + ISimpleInterface instance = (ISimpleInterface)Activator.CreateInstance(iClass); + + instance.ShouldNotBeNull(); + instance.ShouldBeAssignableTo(); + + instance.Id = 42; + instance.Name = "Lorem ipsum"; + + instance.Id.ShouldBe(42); + instance.Name.ShouldBe("Lorem ipsum"); + } + + [TestMethod] + public void CreateTypeForInheritedInterface() + { + Type iClass = DynamicTypeGenerator.GetTypeForInterface(typeof(IInheritedInterface)); + + IInheritedInterface instance = (IInheritedInterface)Activator.CreateInstance(iClass); + + instance.ShouldNotBeNull(); + instance.ShouldBeAssignableTo(); + + instance.Id = 42; + instance.Name = "Lorem ipsum"; + instance.Value = 24; + + instance.Id.ShouldBe(42); + instance.Name.ShouldBe("Lorem ipsum"); + instance.Value.ShouldBe(24); + } + + [TestMethod] + public void CreateTypeForComplexInterface() + { + Type iClass = DynamicTypeGenerator.GetTypeForInterface(typeof(IComplexInterface)); + + IComplexInterface instance = (IComplexInterface)Activator.CreateInstance(iClass); + + instance.ShouldNotBeNull(); + instance.ShouldBeAssignableTo(); + + instance.Id = 42; + instance.Name = "Lorem ipsum"; + instance.WriteOnlyProp = 24; + + instance.Id.ShouldBe(42); + instance.Name.ShouldBe("Lorem ipsum"); + instance.ReadOnlyProp.ShouldBe(0); + + int i = 0; + string s = null; + Should.Throw(() => instance.BaseMethod(), "Call BaseMethod."); + Should.Throw(() => instance.SimpleMethod(), "Call SimpleMethod."); + Should.Throw(() => instance.ComplexMethod(123, ref i, out s), "Call ComplexMethod."); + } + } +} diff --git a/src/Mapster/Utils/DynamicTypeGenerator.cs b/src/Mapster/Utils/DynamicTypeGenerator.cs index ef8b19c9..fa1b4663 100644 --- a/src/Mapster/Utils/DynamicTypeGenerator.cs +++ b/src/Mapster/Utils/DynamicTypeGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using System.Threading; @@ -45,11 +46,22 @@ private static void CheckInterfaceType(Type interfaceType) private static Type CreateTypeForInterface(Type interfaceType) { TypeBuilder builder = _moduleBuilder.DefineType("GeneratedType_" + Interlocked.Increment(ref _generatedCounter)); - builder.AddInterfaceImplementation(interfaceType); - foreach (PropertyInfo prop in interfaceType.GetProperties()) + foreach (Type currentInterface in GetAllInterfaces(interfaceType)) { - CreateProperty(interfaceType, builder, prop); + builder.AddInterfaceImplementation(currentInterface); + foreach (PropertyInfo prop in currentInterface.GetProperties()) + { + CreateProperty(currentInterface, builder, prop); + } + foreach (MethodInfo method in currentInterface.GetMethods()) + { + // MethodAttributes.SpecialName are methods for property getters and setters. + if (!method.Attributes.HasFlag(MethodAttributes.SpecialName)) + { + CreateMethod(builder, method); + } + } } #if NETSTANDARD2_0 @@ -59,9 +71,33 @@ private static Type CreateTypeForInterface(Type interfaceType) #endif } + // GetProperties() and GetMethods() do not return properties/methods from parent interfaces, + // so we need to process every one of them. + private static IEnumerable GetAllInterfaces(Type interfaceType) + { + var allInterfaces = new List(); + var interfaceQueue = new Queue(); + allInterfaces.Add(interfaceType); + interfaceQueue.Enqueue(interfaceType); + while (interfaceQueue.Count > 0) + { + var currentInterface = interfaceQueue.Dequeue(); + foreach (var subInterface in currentInterface.GetInterfaces()) + { + if (allInterfaces.Contains(subInterface)) + { + continue; + } + allInterfaces.Add(subInterface); + interfaceQueue.Enqueue(subInterface); + } + } + return allInterfaces; + } + private static void CreateProperty(Type interfaceType, TypeBuilder builder, PropertyInfo prop) { - const BindingFlags interfacePropMethodsFlags = BindingFlags.Instance | BindingFlags.Public; + const BindingFlags interfacePropMethodFlags = BindingFlags.Instance | BindingFlags.Public; // The property set and get methods require a special set of attributes. const MethodAttributes classPropMethodAttrs = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig; @@ -79,7 +115,7 @@ private static void CreateProperty(Type interfaceType, TypeBuilder builder, Prop propGetIL.Emit(OpCodes.Ldfld, propField); propGetIL.Emit(OpCodes.Ret); - MethodInfo interfaceGetMethod = interfaceType.GetMethod(getMethodName, interfacePropMethodsFlags); + MethodInfo interfaceGetMethod = interfaceType.GetMethod(getMethodName, interfacePropMethodFlags); builder.DefineMethodOverride(propGet, interfaceGetMethod); propBuilder.SetGetMethod(propGet); } @@ -95,10 +131,35 @@ private static void CreateProperty(Type interfaceType, TypeBuilder builder, Prop propSetIL.Emit(OpCodes.Stfld, propField); propSetIL.Emit(OpCodes.Ret); - MethodInfo interfaceSetMethod = interfaceType.GetMethod(setMethodName, interfacePropMethodsFlags); + MethodInfo interfaceSetMethod = interfaceType.GetMethod(setMethodName, interfacePropMethodFlags); builder.DefineMethodOverride(propSet, interfaceSetMethod); propBuilder.SetSetMethod(propSet); } } + + private static void CreateMethod(TypeBuilder builder, MethodInfo interfaceMethod) + { + Type[] parameterTypes = null; + ParameterInfo[] parameters = interfaceMethod.GetParameters(); + if (parameters.Length > 0) + { + parameterTypes = new Type[parameters.Length]; + for (int i = 0; i < parameters.Length; i++) + { + parameterTypes[i] = parameters[i].ParameterType; + } + } + + MethodBuilder classMethod = builder.DefineMethod( + interfaceMethod.Name, + MethodAttributes.Public | MethodAttributes.Virtual, + interfaceMethod.CallingConvention, + interfaceMethod.ReturnType, + parameterTypes); + ILGenerator classMethodIL = classMethod.GetILGenerator(); + classMethodIL.ThrowException(typeof(NotImplementedException)); + + builder.DefineMethodOverride(classMethod, interfaceMethod); + } } } From b9c14a5380c9bf132762f570951de5e461ee955c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stano=20Pe=C5=A5ko?= Date: Sat, 18 Jan 2020 12:34:30 +0100 Subject: [PATCH 4/7] Move GetAllInterfaces to ReflectionUtils --- .../DynamicTypeGeneratorTests.cs | 4 -- src/Mapster/Utils/DynamicTypeGenerator.cs | 27 +----------- src/Mapster/Utils/ReflectionUtils.cs | 43 ++++++++++++++++++- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/Mapster.Tests/DynamicTypeGeneratorTests.cs b/src/Mapster.Tests/DynamicTypeGeneratorTests.cs index 7477e9b3..0101583a 100644 --- a/src/Mapster.Tests/DynamicTypeGeneratorTests.cs +++ b/src/Mapster.Tests/DynamicTypeGeneratorTests.cs @@ -8,7 +8,6 @@ namespace Mapster.Tests [TestClass] public class DynamicTypeGeneratorTests { - private interface INotVisibleInterface { int Id { get; set; } @@ -58,7 +57,6 @@ public void CreateTypeForSimpleInterface() ISimpleInterface instance = (ISimpleInterface)Activator.CreateInstance(iClass); instance.ShouldNotBeNull(); - instance.ShouldBeAssignableTo(); instance.Id = 42; instance.Name = "Lorem ipsum"; @@ -75,7 +73,6 @@ public void CreateTypeForInheritedInterface() IInheritedInterface instance = (IInheritedInterface)Activator.CreateInstance(iClass); instance.ShouldNotBeNull(); - instance.ShouldBeAssignableTo(); instance.Id = 42; instance.Name = "Lorem ipsum"; @@ -94,7 +91,6 @@ public void CreateTypeForComplexInterface() IComplexInterface instance = (IComplexInterface)Activator.CreateInstance(iClass); instance.ShouldNotBeNull(); - instance.ShouldBeAssignableTo(); instance.Id = 42; instance.Name = "Lorem ipsum"; diff --git a/src/Mapster/Utils/DynamicTypeGenerator.cs b/src/Mapster/Utils/DynamicTypeGenerator.cs index fa1b4663..d1c0320d 100644 --- a/src/Mapster/Utils/DynamicTypeGenerator.cs +++ b/src/Mapster/Utils/DynamicTypeGenerator.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using System.Threading; @@ -47,7 +46,7 @@ private static Type CreateTypeForInterface(Type interfaceType) { TypeBuilder builder = _moduleBuilder.DefineType("GeneratedType_" + Interlocked.Increment(ref _generatedCounter)); - foreach (Type currentInterface in GetAllInterfaces(interfaceType)) + foreach (Type currentInterface in ReflectionUtils.GetAllInterfaces(interfaceType)) { builder.AddInterfaceImplementation(currentInterface); foreach (PropertyInfo prop in currentInterface.GetProperties()) @@ -71,30 +70,6 @@ private static Type CreateTypeForInterface(Type interfaceType) #endif } - // GetProperties() and GetMethods() do not return properties/methods from parent interfaces, - // so we need to process every one of them. - private static IEnumerable GetAllInterfaces(Type interfaceType) - { - var allInterfaces = new List(); - var interfaceQueue = new Queue(); - allInterfaces.Add(interfaceType); - interfaceQueue.Enqueue(interfaceType); - while (interfaceQueue.Count > 0) - { - var currentInterface = interfaceQueue.Dequeue(); - foreach (var subInterface in currentInterface.GetInterfaces()) - { - if (allInterfaces.Contains(subInterface)) - { - continue; - } - allInterfaces.Add(subInterface); - interfaceQueue.Enqueue(subInterface); - } - } - return allInterfaces; - } - private static void CreateProperty(Type interfaceType, TypeBuilder builder, PropertyInfo prop) { const BindingFlags interfacePropMethodFlags = BindingFlags.Instance | BindingFlags.Public; diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index 8f418e21..08f10c23 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -84,18 +84,57 @@ public static IEnumerable GetFieldsAndProperties(this Type type, { var bindingFlags = BindingFlags.Instance | accessorFlags; - var properties = type.GetProperties(bindingFlags) + IEnumerable getPropertiesFunc(Type t) => t.GetProperties(bindingFlags) .Where(x => x.GetIndexParameters().Length == 0) .Where(x => !requireSetter || x.CanWrite) .Select(CreateModel); - var fields = type.GetFields(bindingFlags) + IEnumerable getFieldsFunc(Type t) => t.GetFields(bindingFlags) .Where(x => !requireSetter || !x.IsInitOnly) .Select(CreateModel); + IEnumerable properties; + IEnumerable fields; + + if (type.IsInterface) + { + IEnumerable allInterfaces = GetAllInterfaces(type); + properties = allInterfaces.SelectMany(currentInterface => getPropertiesFunc(currentInterface)); + fields = allInterfaces.SelectMany(currentInterface => getFieldsFunc(currentInterface)); + } + else + { + properties = getPropertiesFunc(type); + fields = getFieldsFunc(type); + } + return properties.Concat(fields); } + // GetProperties(), GetFields(), GetMethods() do not return properties/methods from parent interfaces, + // so we need to process every one of them separately. + public static IEnumerable GetAllInterfaces(Type interfaceType) + { + var allInterfaces = new List(); + var interfaceQueue = new Queue(); + allInterfaces.Add(interfaceType); + interfaceQueue.Enqueue(interfaceType); + while (interfaceQueue.Count > 0) + { + var currentInterface = interfaceQueue.Dequeue(); + foreach (var subInterface in currentInterface.GetInterfaces()) + { + if (allInterfaces.Contains(subInterface)) + { + continue; + } + allInterfaces.Add(subInterface); + interfaceQueue.Enqueue(subInterface); + } + } + return allInterfaces; + } + public static bool IsCollection(this Type type) { return typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()) && type != typeof(string); From c23b1c564129db7bc13cb9bc4ff271508744e435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stano=20Pe=C5=A5ko?= Date: Sat, 18 Jan 2020 14:28:13 +0100 Subject: [PATCH 5/7] Add test for mapping to complex interface --- .../DynamicTypeGeneratorTests.cs | 21 +++ src/Mapster.Tests/WhenMappingToInterface.cs | 132 ++++++++++++++++-- 2 files changed, 143 insertions(+), 10 deletions(-) diff --git a/src/Mapster.Tests/DynamicTypeGeneratorTests.cs b/src/Mapster.Tests/DynamicTypeGeneratorTests.cs index 0101583a..ae0a6894 100644 --- a/src/Mapster.Tests/DynamicTypeGeneratorTests.cs +++ b/src/Mapster.Tests/DynamicTypeGeneratorTests.cs @@ -2,6 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Shouldly; using System; +using System.Collections.Generic; namespace Mapster.Tests { @@ -25,17 +26,29 @@ public interface IInheritedInterface : ISimpleInterface int Value { get; set; } } + public class Foo + { + public int Id { get; set; } + public string Name { get; set; } + } + public interface IComplexBaseInterface { int Id { get; set; } + Foo Foo { get; set; } void BaseMethod(); } public interface IComplexInterface : IComplexBaseInterface { string Name { get; set; } + int? NullableInt { get; set; } int ReadOnlyProp { get; } int WriteOnlyProp { set; } + IEnumerable Ints { get; set; } + IEnumerable Foos { get; set; } + int[] IntArray { get; set; } + Foo[] FooArray { get; set; } void SimpleMethod(); int ComplexMethod(byte b, ref int i, out string s); } @@ -93,10 +106,18 @@ public void CreateTypeForComplexInterface() instance.ShouldNotBeNull(); instance.Id = 42; + instance.Foo = new Foo(); + instance.NullableInt = 123; instance.Name = "Lorem ipsum"; instance.WriteOnlyProp = 24; + instance.Ints = new List(); + instance.IntArray = new int[2]; + instance.Foos = new List(); + instance.FooArray = new Foo[2]; instance.Id.ShouldBe(42); + instance.Foo.ShouldNotBeNull(); + instance.NullableInt.ShouldBe(123); instance.Name.ShouldBe("Lorem ipsum"); instance.ReadOnlyProp.ShouldBe(0); diff --git a/src/Mapster.Tests/WhenMappingToInterface.cs b/src/Mapster.Tests/WhenMappingToInterface.cs index 1a204a7a..f7c9b33b 100644 --- a/src/Mapster.Tests/WhenMappingToInterface.cs +++ b/src/Mapster.Tests/WhenMappingToInterface.cs @@ -1,6 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Shouldly; using System; +using System.Collections.Generic; +using System.Linq; namespace Mapster.Tests { @@ -26,9 +28,11 @@ public void MapToInterface() IDto idto = dto.Adapt(); idto.ShouldNotBeNull(); - idto.Id.ShouldBe(dto.Id); - idto.Name.ShouldBe(dto.Name); - idto.UnmappedTarget.ShouldBeNull(); + idto.ShouldSatisfyAllConditions( + () => idto.Id.ShouldBe(dto.Id), + () => idto.Name.ShouldBe(dto.Name), + () => idto.UnmappedTarget.ShouldBeNull() + ); } [TestMethod] @@ -45,10 +49,82 @@ public void MapToInheritedInterface() IInheritedDto idto = dto.Adapt(); idto.ShouldNotBeNull(); - idto.Id.ShouldBe(dto.Id); - idto.Name.ShouldBe(dto.Name); - idto.DateOfBirth.ShouldBe(dto.DateOfBirth); - idto.UnmappedTarget.ShouldBeNull(); + idto.ShouldSatisfyAllConditions( + () => idto.Id.ShouldBe(dto.Id), + () => idto.Name.ShouldBe(dto.Name), + () => idto.DateOfBirth.ShouldBe(dto.DateOfBirth), + () => idto.UnmappedTarget.ShouldBeNull() + ); + } + + [TestMethod] + public void MapToComplexInterface() + { + var subItem = new ComplexDto + { + Name = "Inner lrem ipsum", + Int32 = 420, + Int64 = long.MaxValue, + NullInt1 = null, + NullInt2 = 240, + Floatn = 2.2F, + Doublen = 4.4, + DateTime = new DateTime(1978, 12, 10), + SubItem = null, + Dtos = new List(), + DtoArr = null, + Ints = new List(), + IntArr = null + }; + + var dto = new ComplexDto + { + Name = "Lorem ipsum", + Int32 = 42, + Int64 = long.MaxValue, + NullInt1 = null, + NullInt2 = 24, + Floatn = 1.2F, + Doublen = 2.4, + DateTime = new DateTime(1978, 12, 10), + SubItem = subItem, + Dtos = new List(new[] { subItem, null }), + DtoArr = new ComplexDto[] { null, subItem }, + Ints = new List(new[] { 1, 2 }), + IntArr = new[] { 3, 4 } + }; + + IComplexInterface idto = dto.Adapt(); + + idto.ShouldNotBeNull(); + idto.ShouldSatisfyAllConditions( + () => idto.Name.ShouldBe(dto.Name), + () => idto.Int32.ShouldBe(dto.Int32), + () => idto.Int64.ShouldBe(dto.Int64), + () => idto.NullInt1.ShouldBeNull(), + () => idto.NullInt2.ShouldBe(dto.NullInt2), + () => idto.Floatn.ShouldBe(dto.Floatn), + () => idto.Doublen.ShouldBe(dto.Doublen), + () => idto.DateTime.ShouldBe(dto.DateTime) + ); + idto.SubItem.ShouldSatisfyAllConditions( + () => idto.SubItem.Name.ShouldBe(dto.SubItem.Name), + () => idto.SubItem.Int32.ShouldBe(dto.SubItem.Int32), + () => idto.SubItem.Int64.ShouldBe(dto.SubItem.Int64), + () => idto.SubItem.NullInt1.ShouldBeNull(), + () => idto.SubItem.NullInt2.ShouldBe(dto.SubItem.NullInt2), + () => idto.SubItem.Floatn.ShouldBe(dto.SubItem.Floatn), + () => idto.SubItem.Doublen.ShouldBe(dto.SubItem.Doublen), + () => idto.SubItem.DateTime.ShouldBe(dto.SubItem.DateTime) + ); + idto.ShouldSatisfyAllConditions( + () => idto.Dtos.Count().ShouldBe(dto.Dtos.Count()), + () => idto.DtoArr.Length.ShouldBe(dto.DtoArr.Length), + () => idto.Ints.First().ShouldBe(dto.Ints.First()), + () => idto.Ints.Last().ShouldBe(dto.Ints.Last()), + () => idto.IntArr[0].ShouldBe(dto.IntArr[0]), + () => idto.IntArr[1].ShouldBe(dto.IntArr[1]) + ); } [TestMethod] @@ -64,9 +140,11 @@ public void MapToInterfaceWithMethods() IInterfaceWithMethods idto = dto.Adapt(); idto.ShouldNotBeNull(); - idto.Id.ShouldBe(dto.Id); - idto.Name.ShouldBe(dto.Name); - Should.Throw(() => idto.DoSomething()); + idto.ShouldSatisfyAllConditions( + () => idto.Id.ShouldBe(dto.Id), + () => idto.Name.ShouldBe(dto.Name), + () => Should.Throw(() => idto.DoSomething()) + ); } [TestMethod] @@ -123,5 +201,39 @@ public class InheritedDto public DateTime DateOfBirth { get; set; } public string UnmappedSource { get; set; } } + + public interface IComplexInterface + { + string Name { get; set; } + int Int32 { get; set; } + long Int64 { get; set; } + int? NullInt1 { get; set; } + int? NullInt2 { get; set; } + float Floatn { get; set; } + double Doublen { get; set; } + DateTime DateTime { get; set; } + ComplexDto SubItem { get; set; } + IEnumerable Dtos { get; set; } + ComplexDto[] DtoArr { get; set; } + IEnumerable Ints { get; set; } + int[] IntArr { get; set; } + } + + public class ComplexDto + { + public string Name { get; set; } + public int Int32 { get; set; } + public long Int64 { set; get; } + public int? NullInt1 { get; set; } + public int? NullInt2 { get; set; } + public float Floatn { get; set; } + public double Doublen { get; set; } + public DateTime DateTime { get; set; } + public ComplexDto SubItem { get; set; } + public IEnumerable Dtos { get; set; } + public ComplexDto[] DtoArr { get; set; } + public IEnumerable Ints { get; set; } + public int[] IntArr { get; set; } + } } } From d526e0a3174ac4d6341818cfca5cf15a32310dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stano=20Pe=C5=A5ko?= Date: Sat, 18 Jan 2020 16:53:26 +0100 Subject: [PATCH 6/7] Return back support for NetStandard 1.3 --- src/Mapster/Adapters/BaseAdapter.cs | 4 ++++ src/Mapster/Mapster.csproj | 2 +- src/Mapster/Utils/DynamicTypeGenerator.cs | 10 ++++++++++ src/Mapster/Utils/ReflectionUtils.cs | 6 ++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index bbab0f03..76e8750b 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -333,7 +333,11 @@ protected virtual Expression CreateInstantiationExpression(Expression source, Ex } //if mapping to interface, create dynamic type implementing it +#if NETSTANDARD1_3 + else if (destination is null && arg.DestinationType.IsInterface()) +#else else if (destination is null && arg.DestinationType.IsInterface) +#endif { return Expression.New(DynamicTypeGenerator.GetTypeForInterface(arg.DestinationType)); } diff --git a/src/Mapster/Mapster.csproj b/src/Mapster/Mapster.csproj index f242bbe0..3d9c6a61 100644 --- a/src/Mapster/Mapster.csproj +++ b/src/Mapster/Mapster.csproj @@ -4,7 +4,7 @@ A fast, fun and stimulating object to object mapper. Kind of like AutoMapper, just simpler and way, way faster. Copyright (c) 2016 Chaowlert Chaisrichalermpol, Eric Swann chaowlert;eric_swann - netstandard2.0;net45;net40 + netstandard2.0;netstandard1.3;net45;net40 Mapster Mapster.snk true diff --git a/src/Mapster/Utils/DynamicTypeGenerator.cs b/src/Mapster/Utils/DynamicTypeGenerator.cs index d1c0320d..b2523d66 100644 --- a/src/Mapster/Utils/DynamicTypeGenerator.cs +++ b/src/Mapster/Utils/DynamicTypeGenerator.cs @@ -28,13 +28,21 @@ public static Type GetTypeForInterface(Type interfaceType) private static void CheckInterfaceType(Type interfaceType) { +#if NETSTANDARD1_3 + if (!interfaceType.IsInterface()) +#else if (!interfaceType.IsInterface) +#endif { const string msg = "Cannot create dynamic type for {0}, because it is not an interface.\n" + "Target type full name: {1}"; throw new InvalidOperationException(string.Format(msg, interfaceType.Name, interfaceType.FullName)); } +#if NETSTANDARD1_3 + if (!interfaceType.IsVisible()) +#else if (!interfaceType.IsVisible) +#endif { const string msg = "Cannot adapt to interface {0}, because it is not accessible outside its assembly.\n" + "Interface full name: {1}"; @@ -65,6 +73,8 @@ private static Type CreateTypeForInterface(Type interfaceType) #if NETSTANDARD2_0 return builder.CreateTypeInfo(); +#elif NETSTANDARD1_3 + return builder.CreateTypeInfo().AsType(); #else return builder.CreateType(); #endif diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index 08f10c23..4c253211 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -55,6 +55,8 @@ public static IEnumerable GetCustomAttributesData(this Memb { return member.CustomAttributes; } + public static bool IsInterface(this Type type) => type.GetTypeInfo().IsInterface; + public static bool IsVisible(this Type type) => type.GetTypeInfo().IsVisible; #endif public static bool IsNullable(this Type type) @@ -96,7 +98,11 @@ IEnumerable getFieldsFunc(Type t) => t.GetFields(bindingFlags) IEnumerable properties; IEnumerable fields; +#if NETSTANDARD1_3 + if (type.IsInterface()) +#else if (type.IsInterface) +#endif { IEnumerable allInterfaces = GetAllInterfaces(type); properties = allInterfaces.SelectMany(currentInterface => getPropertiesFunc(currentInterface)); From 7f95158be407be95bf4de85721cd57d7829b56ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stano=20Pe=C5=A5ko?= Date: Sat, 18 Jan 2020 18:53:03 +0100 Subject: [PATCH 7/7] UseInterfaceHierarchy configuration --- src/Mapster.Tests/WhenMappingToInterface.cs | 88 ++++++++++++++++++- src/Mapster/Adapters/BaseClassAdapter.cs | 2 +- src/Mapster/Adapters/DictionaryAdapter.cs | 6 +- .../Settings/ValueAccessingStrategy.cs | 8 +- src/Mapster/TypeAdapterSetter.cs | 19 +++- src/Mapster/TypeAdapterSettings.cs | 14 +-- src/Mapster/Utils/ReflectionUtils.cs | 14 +-- 7 files changed, 129 insertions(+), 22 deletions(-) diff --git a/src/Mapster.Tests/WhenMappingToInterface.cs b/src/Mapster.Tests/WhenMappingToInterface.cs index f7c9b33b..f1164dd3 100644 --- a/src/Mapster.Tests/WhenMappingToInterface.cs +++ b/src/Mapster.Tests/WhenMappingToInterface.cs @@ -36,7 +36,7 @@ public void MapToInterface() } [TestMethod] - public void MapToInheritedInterface() + public void MapToInheritedInterface_InterfaceHierarchyEnabled() { var dto = new InheritedDto { @@ -46,7 +46,10 @@ public void MapToInheritedInterface() UnmappedSource = "Lorem ipsum" }; - IInheritedDto idto = dto.Adapt(); + var config = new TypeAdapterConfig(); + config.Default.UseInterfaceHierarchy(true); + + IInheritedDto idto = dto.Adapt(config); idto.ShouldNotBeNull(); idto.ShouldSatisfyAllConditions( @@ -57,6 +60,79 @@ public void MapToInheritedInterface() ); } + [TestMethod] + public void MapToInheritedInterface_InterfaceHierarchyDisabled() + { + var dto = new InheritedDto + { + Id = 1, + Name = "Test", + DateOfBirth = new DateTime(1978, 12, 10), + UnmappedSource = "Lorem ipsum" + }; + + var config = new TypeAdapterConfig(); + + IInheritedDto idto = dto.Adapt(config); + + idto.ShouldNotBeNull(); + idto.ShouldSatisfyAllConditions( + () => idto.Id.ShouldBe(default), + () => idto.Name.ShouldBeNull(), + () => idto.DateOfBirth.ShouldBe(dto.DateOfBirth), + () => idto.UnmappedTarget.ShouldBeNull() + ); + } + + [TestMethod] + public void MapToInstanceWithInterface_InterfaceHierarchyEnabled() + { + var dto = new InheritedDto + { + Id = 1, + Name = "Test", + DateOfBirth = new DateTime(1978, 12, 10), + UnmappedSource = "Lorem ipsum" + }; + + var config = new TypeAdapterConfig(); + config.Default.UseInterfaceHierarchy(true); + + IInheritedDto target = new ImplementedDto(); + dto.Adapt(target, config); + + target.ShouldNotBeNull(); + target.ShouldSatisfyAllConditions( + () => target.Id.ShouldBe(dto.Id), + () => target.Name.ShouldBe(dto.Name), + () => target.DateOfBirth.ShouldBe(dto.DateOfBirth), + () => target.UnmappedTarget.ShouldBeNull() + ); + } + + [TestMethod] + public void MapToInstanceWithInterface_InterfaceHierarchyDisabled() + { + var dto = new InheritedDto + { + Id = 1, + Name = "Test", + DateOfBirth = new DateTime(1978, 12, 10), + UnmappedSource = "Lorem ipsum" + }; + + IInheritedDto target = new ImplementedDto(); + dto.Adapt(target); + + target.ShouldNotBeNull(); + target.ShouldSatisfyAllConditions( + () => target.Id.ShouldBe(default), + () => target.Name.ShouldBeNull(), + () => target.DateOfBirth.ShouldBe(dto.DateOfBirth), + () => target.UnmappedTarget.ShouldBeNull() + ); + } + [TestMethod] public void MapToComplexInterface() { @@ -202,6 +278,14 @@ public class InheritedDto public string UnmappedSource { get; set; } } + public class ImplementedDto : IInheritedDto + { + public int Id { get; set; } + public string Name { get; set; } + public DateTime DateOfBirth { get; set; } + public string UnmappedTarget { get; set; } + } + public interface IComplexInterface { string Name { get; set; } diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 2258958f..14362baa 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -171,7 +171,7 @@ protected virtual ClassModel GetSetterModel(CompileArgument arg) { return new ClassModel { - Members = arg.DestinationType.GetFieldsAndProperties(requireSetter: true, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public) + Members = arg.DestinationType.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, requireSetter: true, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public) }; } diff --git a/src/Mapster/Adapters/DictionaryAdapter.cs b/src/Mapster/Adapters/DictionaryAdapter.cs index 59c330b5..f1ec5dd6 100644 --- a/src/Mapster/Adapters/DictionaryAdapter.cs +++ b/src/Mapster/Adapters/DictionaryAdapter.cs @@ -26,7 +26,7 @@ protected override bool CanInline(Expression source, Expression? destination, Co return false; //allow inline for dict-to-dict, only when IgnoreNonMapped - return arg.SourceType.GetDictionaryType() == null + return arg.SourceType.GetDictionaryType() == null || arg.Settings.IgnoreNonMapped == true; } @@ -83,7 +83,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio dict.Add(ignore.Key, setWithCondition); } } - + //dict to switch if (dict.Count > 0 || ignores.Count > 0) { @@ -175,7 +175,7 @@ protected override ClassModel GetSetterModel(CompileArgument arg) if (arg.SourceType.GetDictionaryType() == null) { var srcNames = arg.GetSourceNames(); - var propNames = arg.SourceType.GetFieldsAndProperties(accessorFlags: BindingFlags.NonPublic | BindingFlags.Public) + var propNames = arg.SourceType.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public) .Where(model => model.ShouldMapMember(arg, MemberSide.Source)) .Select(model => model.Name) .Where(name => !srcNames.Contains(name)) diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index da731c33..59f36703 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -75,7 +75,7 @@ public static class ValueAccessingStrategy private static Expression? PropertyOrFieldFn(Expression source, IMemberModel destinationMember, CompileArgument arg) { - var members = source.Type.GetFieldsAndProperties(accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); + var members = source.Type.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); var strategy = arg.Settings.NameMatchingStrategy; var destinationMemberName = destinationMember.GetMemberName(arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter); return members @@ -110,7 +110,7 @@ public static class ValueAccessingStrategy private static Expression? GetDeepFlattening(Expression source, string propertyName, CompileArgument arg) { var strategy = arg.Settings.NameMatchingStrategy; - var members = source.Type.GetFieldsAndProperties(accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); + var members = source.Type.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); foreach (var member in members) { if (!member.ShouldMapMember(arg, MemberSide.Source)) @@ -137,7 +137,7 @@ internal static IEnumerable FindUnflatteningPairs(Expression sourc { var strategy = arg.Settings.NameMatchingStrategy; var destinationMemberName = destinationMember.GetMemberName(arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter); - var members = source.Type.GetFieldsAndProperties(accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); + var members = source.Type.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); foreach (var member in members) { @@ -160,7 +160,7 @@ internal static IEnumerable FindUnflatteningPairs(Expression sourc private static IEnumerable GetDeepUnflattening(IMemberModel destinationMember, string propertyName, CompileArgument arg) { var strategy = arg.Settings.NameMatchingStrategy; - var members = destinationMember.Type.GetFieldsAndProperties(accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); + var members = destinationMember.Type.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); foreach (var member in members) { if (!member.ShouldMapMember(arg, MemberSide.Destination)) diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index c2cc75c5..e4a850d3 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -236,6 +236,14 @@ public static class TypeAdapterSetterExtensions setter.Settings.Unflattening = value; return setter; } + + public static TSetter UseInterfaceHierarchy(this TSetter setter, bool value) where TSetter : TypeAdapterSetter + { + setter.CheckCompiled(); + + setter.Settings.UseInterfaceHierarchy = value; + return setter; + } } public class TypeAdapterSetter : TypeAdapterSetter @@ -444,7 +452,7 @@ internal TypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig paren Expression> source, Expression>? shouldMap = null) { this.CheckCompiled(); - + Settings.Resolvers.Add(new InvokerModel { DestinationMemberName = memberName, @@ -553,7 +561,7 @@ internal TypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig paren return this; } - public TypeAdapterSetter Include() + public TypeAdapterSetter Include() where TDerivedSource: class, TSource where TDerivedDestination: class, TDestination { @@ -808,5 +816,12 @@ public TwoWaysTypeAdapterSetter(TypeAdapterConfig config) DestinationToSourceSetter.MaxDepth(value); return this; } + + public TwoWaysTypeAdapterSetter UseInterfaceHierarchy(bool value) + { + SourceToDestinationSetter.UseInterfaceHierarchy(value); + DestinationToSourceSetter.UseInterfaceHierarchy(value); + return this; + } } } diff --git a/src/Mapster/TypeAdapterSettings.cs b/src/Mapster/TypeAdapterSettings.cs index 3a091068..ca79928a 100644 --- a/src/Mapster/TypeAdapterSettings.cs +++ b/src/Mapster/TypeAdapterSettings.cs @@ -1,13 +1,11 @@ -using System; +using Mapster.Models; +using System; using System.Collections.Generic; using System.Linq.Expressions; -using System.Reflection; -using Mapster.Adapters; -using Mapster.Models; namespace Mapster { - public class TypeAdapterSettings: SettingStore + public class TypeAdapterSettings : SettingStore { public IgnoreDictionary Ignore { @@ -69,6 +67,12 @@ public NameMatchingStrategy NameMatchingStrategy set => Set("SkipDestinationMemberCheck", value); } + public bool? UseInterfaceHierarchy + { + get => Get(nameof(UseInterfaceHierarchy)); + set => Set(nameof(UseInterfaceHierarchy), value); + } + public List> ShouldMapMember { get => Get("ShouldMapMember", () => new List>()); diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index 4c253211..1d2eff66 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -79,10 +79,14 @@ public static bool IsPoco(this Type type) if (type.IsConvertible()) return false; - return type.GetFieldsAndProperties(requireSetter: true).Any(); + return type.GetFieldsAndProperties(useInterfaceHierarchy: true, requireSetter: true).Any(); } - public static IEnumerable GetFieldsAndProperties(this Type type, bool requireSetter = false, BindingFlags accessorFlags = BindingFlags.Public) + public static IEnumerable GetFieldsAndProperties( + this Type type, + bool? useInterfaceHierarchy, + bool requireSetter = false, + BindingFlags accessorFlags = BindingFlags.Public) { var bindingFlags = BindingFlags.Instance | accessorFlags; @@ -99,9 +103,9 @@ IEnumerable getFieldsFunc(Type t) => t.GetFields(bindingFlags) IEnumerable fields; #if NETSTANDARD1_3 - if (type.IsInterface()) + if (type.IsInterface() && (useInterfaceHierarchy == true)) #else - if (type.IsInterface) + if (type.IsInterface && (useInterfaceHierarchy == true)) #endif { IEnumerable allInterfaces = GetAllInterfaces(type); @@ -232,7 +236,7 @@ public static bool IsRecordType(this Type type) return false; //no public setter - var props = type.GetFieldsAndProperties().ToList(); + var props = type.GetFieldsAndProperties(useInterfaceHierarchy: true).ToList(); if (props.Any(p => p.SetterModifier == AccessModifier.Public)) return false;