From 6cf545cd3ba67cb48d8991c9ddf7fdaf82d396f0 Mon Sep 17 00:00:00 2001 From: chaowlert Date: Sun, 19 Jan 2020 23:01:06 +0700 Subject: [PATCH] map to readonly interface --- src/Mapster.Tests/WhenMappingToInterface.cs | 26 +++++++++++++++++ src/Mapster/Adapters/ClassAdapter.cs | 5 +++- src/Mapster/Adapters/RecordTypeAdapter.cs | 6 +++- src/Mapster/Utils/DynamicTypeGenerator.cs | 32 +++++++++++++++++---- src/Mapster/Utils/ReflectionUtils.cs | 4 +++ 5 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/Mapster.Tests/WhenMappingToInterface.cs b/src/Mapster.Tests/WhenMappingToInterface.cs index c96b620f..4c61bf2a 100644 --- a/src/Mapster.Tests/WhenMappingToInterface.cs +++ b/src/Mapster.Tests/WhenMappingToInterface.cs @@ -84,6 +84,26 @@ public void MapToInstanceWithInterface() ); } + [TestMethod] + public void MapToReadOnlyInterface() + { + var dto = new Dto + { + Id = 1, + Name = "Test", + UnmappedSource = "Lorem ipsum" + }; + + var idto = dto.Adapt(); + + idto.ShouldNotBeNull(); + idto.ShouldSatisfyAllConditions( + () => idto.Id.ShouldBe(dto.Id), + () => idto.Name.ShouldBe(dto.Name) + ); + } + + [TestMethod] public void MapToComplexInterface() { @@ -195,6 +215,12 @@ private interface INotVisibleInterface string Name { get; set; } } + public interface IReadOnlyInterface + { + int Id { get; } + string Name { get; } + } + public interface IInterfaceWithMethods { int Id { get; set; } diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index 90953a71..0217ce46 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -61,7 +61,10 @@ protected override Expression CreateInstantiationExpression(Expression source, E var ctor = arg.Settings.MapToConstructor as ConstructorInfo; if (ctor == null) { - classConverter = arg.DestinationType.GetConstructors() + var destType = arg.DestinationType.GetTypeInfo().IsInterface + ? DynamicTypeGenerator.GetTypeForInterface(arg.DestinationType) + : arg.DestinationType; + classConverter = destType.GetConstructors() .OrderByDescending(it => it.GetParameters().Length) .Select(it => GetConstructorModel(it, true)) .Select(it => CreateClassConverter(source, it, arg)) diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index 5b670c73..3dc48b86 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -1,5 +1,6 @@ using System.Linq.Expressions; using System.Reflection; +using Mapster.Utils; namespace Mapster.Adapters { @@ -20,7 +21,10 @@ protected override Expression CreateInstantiationExpression(Expression source, E if (arg.GetConstructUsing() != null) return base.CreateInstantiationExpression(source, destination, arg); - var ctor = arg.DestinationType.GetConstructors()[0]; + var destType = arg.DestinationType.GetTypeInfo().IsInterface + ? DynamicTypeGenerator.GetTypeForInterface(arg.DestinationType) + : arg.DestinationType; + var ctor = destType.GetConstructors()[0]; var classModel = GetConstructorModel(ctor, false); var classConverter = CreateClassConverter(source, classModel, arg); return CreateInstantiationExpression(source, classConverter, arg); diff --git a/src/Mapster/Utils/DynamicTypeGenerator.cs b/src/Mapster/Utils/DynamicTypeGenerator.cs index 8d9a6d73..5266fddd 100644 --- a/src/Mapster/Utils/DynamicTypeGenerator.cs +++ b/src/Mapster/Utils/DynamicTypeGenerator.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Threading; @@ -8,7 +10,7 @@ namespace Mapster.Utils { internal static class DynamicTypeGenerator { - private const string DynamicAssemblyName = "MapsterGeneratedTypes"; + private const string DynamicAssemblyName = "Mapster.Dynamic"; private static readonly AssemblyBuilder _assemblyBuilder = #if NET40 @@ -16,7 +18,7 @@ internal static class DynamicTypeGenerator #else AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(DynamicAssemblyName), AssemblyBuilderAccess.Run); #endif - private static readonly ModuleBuilder _moduleBuilder = _assemblyBuilder.DefineDynamicModule("Classes"); + private static readonly ModuleBuilder _moduleBuilder = _assemblyBuilder.DefineDynamicModule("Mapster.Dynamic"); private static readonly ConcurrentDictionary _generated = new ConcurrentDictionary(); private static int _generatedCounter; @@ -44,9 +46,13 @@ private static Type CreateTypeForInterface(Type interfaceType) foreach (Type currentInterface in ReflectionUtils.GetAllInterfaces(interfaceType)) { builder.AddInterfaceImplementation(currentInterface); + var args = new List(); foreach (PropertyInfo prop in currentInterface.GetProperties()) { - CreateProperty(currentInterface, builder, prop); + FieldBuilder propField = builder.DefineField("_" + NameMatchingStrategy.CamelCase(prop.Name), prop.PropertyType, FieldAttributes.Private); + CreateProperty(currentInterface, builder, prop, propField); + if (!prop.CanWrite) + args.Add(propField); } foreach (MethodInfo method in currentInterface.GetMethods()) { @@ -56,6 +62,23 @@ private static Type CreateTypeForInterface(Type interfaceType) CreateMethod(builder, method); } } + + if (args.Count > 0) + { + var ctorBuilder = builder.DefineConstructor(MethodAttributes.Public, + CallingConventions.Standard, + args.Select(it => it.FieldType).ToArray()); + var ctorIL = ctorBuilder.GetILGenerator(); + for (var i = 0; i < args.Count; i++) + { + var arg = args[i]; + ctorBuilder.DefineParameter(i + 1, ParameterAttributes.None, arg.Name.Substring(1)); + ctorIL.Emit(OpCodes.Ldarg_0); + ctorIL.Emit(OpCodes.Ldarg_S, i + 1); + ctorIL.Emit(OpCodes.Stfld, arg); + } + ctorIL.Emit(OpCodes.Ret); + } } #if NETSTANDARD2_0 @@ -67,14 +90,13 @@ private static Type CreateTypeForInterface(Type interfaceType) #endif } - private static void CreateProperty(Type interfaceType, TypeBuilder builder, PropertyInfo prop) + private static void CreateProperty(Type interfaceType, TypeBuilder builder, PropertyInfo prop, FieldBuilder propField) { 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; - FieldBuilder propField = builder.DefineField("_" + prop.Name, prop.PropertyType, FieldAttributes.Private); PropertyBuilder propBuilder = builder.DefineProperty(prop.Name, PropertyAttributes.None, prop.PropertyType, null); if (prop.CanRead) diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index 480b407a..e564b74e 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -227,6 +227,10 @@ public static bool IsRecordType(this Type type) if (props.Any(p => p.SetterModifier == AccessModifier.Public)) return false; + //interface, ctor will automatically created + if (type.GetTypeInfo().IsInterface) + return true; + //1 non-empty constructor var ctors = type.GetConstructors().Where(ctor => ctor.GetParameters().Length > 0).ToList(); if (ctors.Count != 1)