Skip to content

Commit a0f2220

Browse files
committed
- allow map to target without default constructor (sp thx to Burgyn)
- throw when pass nest destination member access to Map method
1 parent 266667f commit a0f2220

File tree

5 files changed

+45
-25
lines changed

5 files changed

+45
-25
lines changed

src/Mapster.Tests/WhenUsingNonDefaultConstructor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void Map_To_Existing_Destination_Instance_Should_Pass()
7373
}
7474

7575
[TestMethod]
76-
public void Map_To_Destination_Type_Without_Default_Constructor_Shoud_Throw_Argument_Exception()
76+
public void Map_To_Destination_Type_Without_Default_Constructor_Shoud_Throw_Exception()
7777
{
7878
var simplePoco = new SimplePoco { Id = Guid.NewGuid(), Name = "TestName" };
7979

@@ -83,7 +83,7 @@ public void Map_To_Destination_Type_Without_Default_Constructor_Shoud_Throw_Argu
8383
};
8484

8585
action.ShouldThrow<CompileException>()
86-
.InnerException.ShouldBeOfType<ArgumentException>();
86+
.InnerException.ShouldBeOfType<InvalidOperationException>();
8787
}
8888

8989
#region TestClasses

src/Mapster/Adapters/BaseAdapter.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,21 +272,30 @@ protected Expression CreateInstantiationExpression(Expression source, CompileArg
272272
protected virtual Expression CreateInstantiationExpression(Expression source, Expression destination, CompileArgument arg)
273273
{
274274
//new TDestination()
275+
276+
//if there is constructUsing, use constructUsing
275277
var constructUsing = arg.Settings.ConstructUsingFactory?.Invoke(arg);
276278
Expression newObj;
277279
if (constructUsing != null)
278280
newObj = constructUsing.Apply(source).TrimConversion(true).To(arg.DestinationType);
279-
else if (arg.DestinationType.GetTypeInfo().IsAbstract && arg.Settings.Includes.Count > 0)
281+
282+
//if there is default constructor, use default constructor
283+
else if (arg.DestinationType.HasDefaultConstructor())
284+
newObj = Expression.New(arg.DestinationType);
285+
286+
//if mapToTarget or include derived types, allow mapping & throw exception on runtime
287+
//instantiation is not needed
288+
else if (destination != null || arg.Settings.Includes.Count > 0)
280289
newObj = Expression.Throw(
281290
Expression.New(
282291
// ReSharper disable once AssignNullToNotNullAttribute
283292
typeof(InvalidOperationException).GetConstructor(new[] { typeof(string) }),
284-
Expression.Constant("Cannot instantiate abstract type: " + arg.DestinationType.Name)),
293+
Expression.Constant("Cannot instantiate type: " + arg.DestinationType.Name)),
285294
arg.DestinationType);
286-
else if (destination == null || arg.DestinationType.HasDefaultConstructor())
287-
newObj = Expression.New(arg.DestinationType);
295+
296+
//otherwise throw
288297
else
289-
newObj = destination;
298+
throw new InvalidOperationException($"No default constructor for type '{arg.DestinationType.Name}', please use 'ConstructUsing'");
290299

291300
//dest ?? new TDest();
292301
return destination == null

src/Mapster/Extensions.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Linq;
33
using System.Linq.Expressions;
4+
using System.Reflection;
45
using Mapster.Models;
56

67
namespace Mapster
@@ -22,7 +23,7 @@ public static bool HasCustomAttribute(this IMemberModel member, Type type)
2223

2324
public static T GetCustomAttribute<T>(this IMemberModel member)
2425
{
25-
return (T)member.GetCustomAttributes(true).FirstOrDefault(attr => attr is T);
26+
return (T) member.GetCustomAttributes(true).FirstOrDefault(attr => attr is T);
2627
}
2728

2829
/// <summary>
@@ -33,7 +34,15 @@ public static T GetCustomAttribute<T>(this IMemberModel member)
3334
/// <c>true</c> if specific <paramref name="type"/> has default constructor; otherwise <c>false</c>.
3435
/// </returns>
3536
public static bool HasDefaultConstructor(this Type type)
36-
=> type.IsValueType || type.GetConstructor(Type.EmptyTypes) != null;
37+
{
38+
if (type == typeof(void)
39+
|| type.GetTypeInfo().IsAbstract
40+
|| type.GetTypeInfo().IsInterface)
41+
return false;
42+
if (type.GetTypeInfo().IsValueType)
43+
return true;
44+
return type.GetConstructor(Type.EmptyTypes) != null;
45+
}
3746

3847
}
3948
}

src/Mapster/Mapster.NetCore.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
<PackageId>Mapster</PackageId>
1313
<PackageTags>Mapper;AutoMapper;Fast;Mapping</PackageTags>
1414
<PackageIconUrl>https://cloud.githubusercontent.com/assets/5763993/26522718/d16f3e42-4330-11e7-9b78-f8c7402624e7.png</PackageIconUrl>
15-
<PackageProjectUrl>https://github.com/chaowlert/Mapster</PackageProjectUrl>
16-
<PackageLicenseUrl>https://github.com/chaowlert/Mapster/blob/master/LICENSE</PackageLicenseUrl>
15+
<PackageProjectUrl>https://github.com/MapsterMapper/Mapster</PackageProjectUrl>
16+
<PackageLicenseUrl>https://github.com/MapsterMapper/Mapster/blob/master/LICENSE</PackageLicenseUrl>
1717
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.3' ">1.6.0</NetStandardImplicitPackageVersion>
1818
<GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
1919
<GenerateAssemblyDescriptionAttribute>false</GenerateAssemblyDescriptionAttribute>
@@ -22,7 +22,7 @@
2222
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
2323
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
2424
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
25-
<Version>3.1.6</Version>
25+
<Version>3.1.7</Version>
2626
<RootNamespace>Mapster</RootNamespace>
2727
</PropertyGroup>
2828

src/Mapster/Utils/ReflectionUtils.cs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,26 +128,28 @@ public static Type UnwrapNullable(this Type type)
128128
return type.IsNullable() ? type.GetGenericArguments()[0] : type;
129129
}
130130

131-
public static MemberExpression GetMemberInfo(Expression method, bool noThrow = false)
131+
public static MemberExpression GetMemberInfo(Expression member, bool source = false)
132132
{
133-
var lambda = method as LambdaExpression;
133+
var lambda = member as LambdaExpression;
134134
if (lambda == null)
135-
throw new ArgumentNullException(nameof(method));
136-
137-
MemberExpression memberExpr = null;
135+
throw new ArgumentNullException(nameof(member));
138136

137+
var expr = lambda.Body;
139138
if (lambda.Body.NodeType == ExpressionType.Convert)
139+
expr = ((UnaryExpression)lambda.Body).Operand;
140+
141+
MemberExpression memberExpr = null;
142+
if (expr.NodeType == ExpressionType.MemberAccess)
140143
{
141-
memberExpr =
142-
((UnaryExpression) lambda.Body).Operand as MemberExpression;
143-
}
144-
else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
145-
{
146-
memberExpr = lambda.Body as MemberExpression;
144+
var tmp = (MemberExpression) expr;
145+
if (tmp.Expression.NodeType == ExpressionType.Parameter)
146+
memberExpr = tmp;
147+
else if (!source)
148+
throw new ArgumentException("Only first level member access on destination allowed (eg. dest => dest.Name)", nameof(member));
147149
}
148150

149-
if (memberExpr == null && !noThrow)
150-
throw new ArgumentException("argument must be member access", nameof(method));
151+
if (memberExpr == null && !source)
152+
throw new ArgumentException("Argument must be member access", nameof(member));
151153

152154
return memberExpr;
153155
}

0 commit comments

Comments
 (0)