Skip to content

Commit 9984029

Browse files
authored
Merge pull request #817 from DocSvartz/Add-Ctor-nullable-param-fix-publish-2
Fix #811 - Add Constructor param source null checker
2 parents e815782 + a2a19f1 commit 9984029

File tree

4 files changed

+279
-2
lines changed

4 files changed

+279
-2
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using Microsoft.VisualStudio.TestTools.UnitTesting;
2+
using Shouldly;
3+
4+
namespace Mapster.Tests
5+
{
6+
[TestClass]
7+
public class WhenCtorNullableParamMapping
8+
{
9+
[TestMethod]
10+
public void Dto_To_Domain_MapsCorrectly()
11+
{
12+
var config = new TypeAdapterConfig();
13+
14+
config.Default.MapToConstructor(true);
15+
config
16+
.NewConfig<AbstractDtoTestClass, AbstractDomainTestClass>()
17+
.Include<DerivedDtoTestClass, DerivedDomainTestClass>();
18+
19+
20+
var dtoDerived = new DerivedDtoTestClass
21+
{
22+
DerivedProperty = "DerivedValue",
23+
AbstractProperty = "AbstractValue"
24+
};
25+
26+
var dto = new DtoTestClass
27+
{
28+
AbstractType = dtoDerived
29+
};
30+
31+
var domain = dto.Adapt<DomainTestClass>(config);
32+
33+
domain.AbstractType.ShouldNotBe(null);
34+
domain.AbstractType.ShouldBeOfType<DerivedDomainTestClass>();
35+
36+
var domainDerived = (DerivedDomainTestClass)domain.AbstractType;
37+
domainDerived.DerivedProperty.ShouldBe(dtoDerived.DerivedProperty);
38+
domainDerived.AbstractProperty.ShouldBe(dtoDerived.AbstractProperty);
39+
40+
}
41+
42+
[TestMethod]
43+
public void Dto_To_Domain_AbstractClassNull_MapsCorrectly()
44+
{
45+
var config = new TypeAdapterConfig();
46+
47+
config.Default.MapToConstructor(true);
48+
config
49+
.NewConfig<AbstractDtoTestClass, AbstractDomainTestClass>()
50+
.Include<DerivedDtoTestClass, DerivedDomainTestClass>();
51+
52+
var dto = new DtoTestClass
53+
{
54+
AbstractType = null
55+
};
56+
57+
var domain = dto.Adapt<DomainTestClass>(config);
58+
59+
domain.AbstractType.ShouldBeNull();
60+
}
61+
62+
63+
#region Immutable classes with private setters, map via ctors
64+
private abstract class AbstractDomainTestClass
65+
{
66+
public string AbstractProperty { get; private set; }
67+
68+
protected AbstractDomainTestClass(string abstractProperty)
69+
{
70+
AbstractProperty = abstractProperty;
71+
}
72+
}
73+
74+
private class DerivedDomainTestClass : AbstractDomainTestClass
75+
{
76+
public string DerivedProperty { get; private set; }
77+
78+
/// <inheritdoc />
79+
public DerivedDomainTestClass(string abstractProperty, string derivedProperty)
80+
: base(abstractProperty)
81+
{
82+
DerivedProperty = derivedProperty;
83+
}
84+
}
85+
86+
private class DomainTestClass
87+
{
88+
public AbstractDomainTestClass? AbstractType { get; private set; }
89+
90+
public DomainTestClass(
91+
AbstractDomainTestClass? abstractType)
92+
{
93+
AbstractType = abstractType;
94+
}
95+
}
96+
#endregion
97+
98+
#region DTO classes
99+
private abstract class AbstractDtoTestClass
100+
{
101+
public string AbstractProperty { get; set; }
102+
}
103+
104+
private class DerivedDtoTestClass : AbstractDtoTestClass
105+
{
106+
public string DerivedProperty { get; set; }
107+
}
108+
109+
private class DtoTestClass
110+
{
111+
public AbstractDtoTestClass? AbstractType { get; set; }
112+
}
113+
#endregion
114+
}
115+
}

src/Mapster/Adapters/BaseClassAdapter.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,17 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi
226226
}
227227
else
228228
{
229-
getter = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member);
229+
230+
if (member.Getter.CanBeNull() && member.Ignore.Condition == null)
231+
{
232+
var compareNull = Expression.Equal(member.Getter, Expression.Constant(null, member.Getter.Type));
233+
getter = Expression.Condition(ExpressionEx.Not(compareNull),
234+
CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member),
235+
defaultConst);
236+
}
237+
else
238+
getter = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member);
239+
230240
if (member.Ignore.Condition != null)
231241
{
232242
var body = member.Ignore.IsChildPath

src/Mapster/Adapters/ClassAdapter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ private static Expression SetValueByReflection(MemberMapping member, MemberExpre
201201
var exp = CreateInstantiationExpression(source, arg);
202202
var memberInit = exp as MemberInitExpression;
203203
var newInstance = memberInit?.NewExpression ?? (NewExpression)exp;
204-
var contructorMembers = newInstance.Arguments.OfType<MemberExpression>().Select(me => me.Member).ToArray();
204+
var contructorMembers = newInstance.GetAllMemberExpressionsMemberInfo().ToArray();
205205
ClassModel? classModel;
206206
ClassMapping? classConverter;
207207
if (IsRequiredOnly)
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using System.Collections.Generic;
2+
using System.Linq.Expressions;
3+
using System.Reflection;
4+
5+
public static class MemberExpressionExtractor
6+
{
7+
public static IEnumerable<MemberInfo> GetAllMemberExpressionsMemberInfo(this Expression expression)
8+
{
9+
var result = new List<MemberInfo>();
10+
CollectMemberInfos(expression, result);
11+
return result;
12+
}
13+
14+
private static void CollectMemberInfos(Expression expression, ICollection<MemberInfo> results)
15+
{
16+
if (expression == null) return;
17+
18+
if (expression is MemberExpression memberExpression)
19+
{
20+
results.Add(memberExpression.Member);
21+
}
22+
else if (expression is BinaryExpression binaryExpression && binaryExpression.NodeType == ExpressionType.Assign)
23+
{
24+
ProcessBinaryAssign(binaryExpression, results);
25+
}
26+
27+
foreach (var subExpression in expression.GetSubExpressions())
28+
{
29+
CollectMemberInfos(subExpression, results);
30+
}
31+
}
32+
33+
private static void ProcessBinaryAssign(BinaryExpression assignExpression, ICollection<MemberInfo> results)
34+
{
35+
if (assignExpression == null) return;
36+
37+
if (assignExpression.Left is MemberExpression leftMember)
38+
{
39+
results.Add(leftMember.Member);
40+
}
41+
42+
CollectMemberInfos(assignExpression.Right, results);
43+
}
44+
45+
private static IEnumerable<Expression> GetSubExpressions(this Expression expression)
46+
{
47+
if (expression == null) yield break;
48+
49+
switch (expression.NodeType)
50+
{
51+
case ExpressionType.MemberAccess:
52+
yield return ((MemberExpression)expression).Expression;
53+
break;
54+
55+
case ExpressionType.Call:
56+
foreach (var arg in ((MethodCallExpression)expression).Arguments)
57+
yield return arg;
58+
yield return ((MethodCallExpression)expression).Object;
59+
break;
60+
61+
case ExpressionType.Lambda:
62+
yield return ((LambdaExpression)expression).Body;
63+
break;
64+
65+
case ExpressionType.Block:
66+
foreach (var blockExpr in ((BlockExpression)expression).Expressions)
67+
yield return blockExpr;
68+
break;
69+
70+
case ExpressionType.Conditional:
71+
yield return ((ConditionalExpression)expression).Test;
72+
yield return ((ConditionalExpression)expression).IfTrue;
73+
yield return ((ConditionalExpression)expression).IfFalse;
74+
break;
75+
76+
case ExpressionType.NewArrayInit or ExpressionType.NewArrayBounds:
77+
foreach (var arrayItem in ((NewArrayExpression)expression).Expressions)
78+
yield return arrayItem;
79+
break;
80+
81+
case ExpressionType.New:
82+
foreach (var arg in ((NewExpression)expression).Arguments)
83+
yield return arg;
84+
break;
85+
86+
case ExpressionType.Invoke:
87+
yield return ((InvocationExpression)expression).Expression;
88+
break;
89+
90+
case ExpressionType.Assign:
91+
yield return ((BinaryExpression)expression).Left;
92+
yield return ((BinaryExpression)expression).Right;
93+
break;
94+
95+
case ExpressionType.Add:
96+
case ExpressionType.Subtract:
97+
case ExpressionType.Multiply:
98+
case ExpressionType.Divide:
99+
case ExpressionType.Equal:
100+
case ExpressionType.NotEqual:
101+
case ExpressionType.GreaterThan:
102+
case ExpressionType.LessThan:
103+
case ExpressionType.AndAlso:
104+
case ExpressionType.OrElse:
105+
yield return ((BinaryExpression)expression).Left;
106+
yield return ((BinaryExpression)expression).Right;
107+
break;
108+
109+
case ExpressionType.Not:
110+
case ExpressionType.Negate:
111+
case ExpressionType.Convert:
112+
case ExpressionType.Increment:
113+
case ExpressionType.Decrement:
114+
case ExpressionType.Quote:
115+
case ExpressionType.TypeAs:
116+
case ExpressionType.OnesComplement:
117+
yield return ((UnaryExpression)expression).Operand;
118+
break;
119+
120+
case ExpressionType.TypeIs:
121+
yield return ((TypeBinaryExpression)expression).Expression;
122+
break;
123+
124+
case ExpressionType.Coalesce:
125+
yield return ((BinaryExpression)expression).Left;
126+
yield return ((BinaryExpression)expression).Conversion;
127+
yield return ((BinaryExpression)expression).Right;
128+
break;
129+
130+
case ExpressionType.Index:
131+
yield return ((IndexExpression)expression).Object;
132+
foreach (var indexArg in ((IndexExpression)expression).Arguments)
133+
yield return indexArg;
134+
break;
135+
136+
case ExpressionType.Loop:
137+
yield return ((LoopExpression)expression).Body;
138+
break;
139+
140+
case ExpressionType.Try:
141+
yield return ((TryExpression)expression).Body;
142+
foreach (var handler in ((TryExpression)expression).Handlers)
143+
yield return handler.Body;
144+
yield return ((TryExpression)expression).Finally;
145+
break;
146+
147+
148+
default:
149+
break;
150+
}
151+
}
152+
}

0 commit comments

Comments
 (0)