Skip to content

Commit

Permalink
map to readonly interface
Browse files Browse the repository at this point in the history
  • Loading branch information
chaowlert committed Jan 19, 2020
1 parent 5158f08 commit 6cf545c
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 7 deletions.
26 changes: 26 additions & 0 deletions src/Mapster.Tests/WhenMappingToInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IReadOnlyInterface>();

idto.ShouldNotBeNull();
idto.ShouldSatisfyAllConditions(
() => idto.Id.ShouldBe(dto.Id),
() => idto.Name.ShouldBe(dto.Name)
);
}


[TestMethod]
public void MapToComplexInterface()
{
Expand Down Expand Up @@ -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; }
Expand Down
5 changes: 4 additions & 1 deletion src/Mapster/Adapters/ClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
6 changes: 5 additions & 1 deletion src/Mapster/Adapters/RecordTypeAdapter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Linq.Expressions;
using System.Reflection;
using Mapster.Utils;

namespace Mapster.Adapters
{
Expand All @@ -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);
Expand Down
32 changes: 27 additions & 5 deletions src/Mapster/Utils/DynamicTypeGenerator.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,15 +10,15 @@ 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
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 ModuleBuilder _moduleBuilder = _assemblyBuilder.DefineDynamicModule("Mapster.Dynamic");
private static readonly ConcurrentDictionary<Type, Type> _generated = new ConcurrentDictionary<Type, Type>();
private static int _generatedCounter;

Expand Down Expand Up @@ -44,9 +46,13 @@ private static Type CreateTypeForInterface(Type interfaceType)
foreach (Type currentInterface in ReflectionUtils.GetAllInterfaces(interfaceType))
{
builder.AddInterfaceImplementation(currentInterface);
var args = new List<FieldBuilder>();
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())
{
Expand All @@ -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
Expand All @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions src/Mapster/Utils/ReflectionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 6cf545c

Please sign in to comment.