Skip to content

Commit a00dfa8

Browse files
committed
Add support .Inherits() and .Include() to map OpenGeneric
1 parent 0e27928 commit a00dfa8

File tree

6 files changed

+168
-28
lines changed

6 files changed

+168
-28
lines changed

src/Mapster.Tests/WhenMappingWithOpenGenerics.cs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
using Microsoft.VisualStudio.TestTools.UnitTesting;
22
using Shouldly;
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Text;
7-
using System.Threading.Tasks;
83

94
namespace Mapster.Tests
105
{
@@ -36,6 +31,60 @@ public void Setting_From_OpenGeneric_Has_No_SideEffect()
3631
var cCopy = c.Adapt<C>(config);
3732
}
3833

34+
[TestMethod]
35+
public void MapOpenGenericsUseInherits()
36+
{
37+
TypeAdapterConfig.GlobalSettings
38+
.ForType(typeof(GenericPoco<>), typeof(GenericDto<>))
39+
.Map("value", "Value");
40+
41+
TypeAdapterConfig.GlobalSettings
42+
.ForType(typeof(DerivedPoco<>), typeof(DerivedDto<>))
43+
.Map("derivedValue", "DerivedValue")
44+
.Inherits(typeof(GenericPoco<>), typeof(GenericDto<>));
45+
46+
var poco = new DerivedPoco<int> { Value = 123 , DerivedValue = 42 };
47+
var dto = poco.Adapt<DerivedDto<int>>();
48+
dto.value.ShouldBe(poco.Value);
49+
dto.derivedValue.ShouldBe(poco.DerivedValue);
50+
}
51+
52+
[TestMethod]
53+
public void MapOpenGenericsUseInclude()
54+
{
55+
TypeAdapterConfig.GlobalSettings.Clear();
56+
57+
TypeAdapterConfig.GlobalSettings
58+
.ForType(typeof(DerivedPoco<>), typeof(DerivedDto<>))
59+
.Map("derivedValue", "DerivedValue");
60+
61+
TypeAdapterConfig.GlobalSettings
62+
.ForType(typeof(GenericPoco<>), typeof(GenericDto<>))
63+
.Map("value", "Value");
64+
65+
TypeAdapterConfig.GlobalSettings
66+
.ForType(typeof(GenericPoco<>), typeof(GenericDto<>))
67+
.Include(typeof(DerivedPoco<>), typeof(DerivedDto<>));
68+
69+
var poco = new DerivedPoco<int> { Value = 123, DerivedValue = 42 };
70+
var dto = poco.Adapt(typeof(GenericPoco<>), typeof(GenericDto<>));
71+
72+
dto.ShouldBeOfType<DerivedDto<int>>();
73+
74+
((DerivedDto<int>)dto).value.ShouldBe(poco.Value);
75+
((DerivedDto<int>)dto).derivedValue.ShouldBe(poco.DerivedValue);
76+
}
77+
78+
public class DerivedPoco<T> : GenericPoco<T>
79+
{
80+
public T DerivedValue { get; set; }
81+
}
82+
83+
public class DerivedDto<T> : GenericDto<T>
84+
{
85+
public T derivedValue { get; set; }
86+
}
87+
3988
public class GenericPoco<T>
4089
{
4190
public T Value { get; set; }

src/Mapster/Adapters/BaseAdapter.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -174,39 +174,47 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de
174174
// return adapt<TSource, TDest>(drvdSource);
175175
foreach (var tuple in arg.Settings.Includes)
176176
{
177+
TypeTuple itemTuple = tuple;
178+
179+
if (tuple.Source.IsOpenGenericType() && tuple.Destination.IsOpenGenericType())
180+
{
181+
var genericArg = source.Type.GetGenericArguments();
182+
itemTuple = new TypeTuple(tuple.Source.MakeGenericType(genericArg), tuple.Destination.MakeGenericType(genericArg));
183+
}
184+
177185
//same type, no redirect to prevent endless loop
178-
if (tuple.Source == arg.SourceType)
186+
if (itemTuple.Source == arg.SourceType)
179187
continue;
180188

181189
//type is not compatible, no redirect
182-
if (!arg.SourceType.GetTypeInfo().IsAssignableFrom(tuple.Source.GetTypeInfo()))
190+
if (!arg.SourceType.GetTypeInfo().IsAssignableFrom(itemTuple.Source.GetTypeInfo()))
183191
continue;
184192

185-
var drvdSource = Expression.Variable(tuple.Source);
193+
var drvdSource = Expression.Variable(itemTuple.Source);
186194
vars.Add(drvdSource);
187195

188196
var drvdSourceAssign = Expression.Assign(
189197
drvdSource,
190-
Expression.TypeAs(source, tuple.Source));
198+
Expression.TypeAs(source, itemTuple.Source));
191199
blocks.Add(drvdSourceAssign);
192-
var cond = Expression.NotEqual(drvdSource, Expression.Constant(null, tuple.Source));
200+
var cond = Expression.NotEqual(drvdSource, Expression.Constant(null, itemTuple.Source));
193201

194202
ParameterExpression? drvdDest = null;
195203
if (destination != null)
196204
{
197-
drvdDest = Expression.Variable(tuple.Destination);
205+
drvdDest = Expression.Variable(itemTuple.Destination);
198206
vars.Add(drvdDest);
199207

200208
var drvdDestAssign = Expression.Assign(
201209
drvdDest,
202-
Expression.TypeAs(destination, tuple.Destination));
210+
Expression.TypeAs(destination, itemTuple.Destination));
203211
blocks.Add(drvdDestAssign);
204212
cond = Expression.AndAlso(
205213
cond,
206-
Expression.NotEqual(drvdDest, Expression.Constant(null, tuple.Destination)));
214+
Expression.NotEqual(drvdDest, Expression.Constant(null, itemTuple.Destination)));
207215
}
208216

209-
var adaptExpr = CreateAdaptExpressionCore(drvdSource, tuple.Destination, arg, destination: drvdDest);
217+
var adaptExpr = CreateAdaptExpressionCore(drvdSource, itemTuple.Destination, arg, destination: drvdDest);
210218
var adapt = Expression.Return(label, adaptExpr);
211219
var ifExpr = Expression.IfThen(cond, adapt);
212220
blocks.Add(ifExpr);

src/Mapster/TypeAdapter.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ private static TDestination UpdateFuncFromPackedinObject<TSource, TDestination>(
136136
/// <returns>Adapted destination type.</returns>
137137
public static object? Adapt(this object source, Type sourceType, Type destinationType)
138138
{
139+
if (source != null &&
140+
sourceType.IsOpenGenericType() && destinationType.IsOpenGenericType())
141+
{
142+
var arg = source.GetType().GetGenericArguments();
143+
return Adapt(source, sourceType.MakeGenericType(arg), destinationType.MakeGenericType(arg), TypeAdapterConfig.GlobalSettings);
144+
}
139145
return Adapt(source, sourceType, destinationType, TypeAdapterConfig.GlobalSettings);
140146
}
141147

src/Mapster/TypeAdapterConfig.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ namespace Mapster
1313
{
1414
public class TypeAdapterConfig
1515
{
16+
public Type SourceType { get; protected set; }
17+
public Type DestinationType { get; protected set; }
1618
public static List<TypeAdapterRule> RulesTemplate { get; } = CreateRuleTemplate();
1719
public static TypeAdapterConfig GlobalSettings { get; } = new TypeAdapterConfig();
1820

@@ -148,6 +150,9 @@ public TypeAdapterSetter When(Func<PreCompileArgument, bool> canMap)
148150
/// <returns></returns>
149151
public TypeAdapterSetter<TSource, TDestination> NewConfig<TSource, TDestination>()
150152
{
153+
this.SourceType = typeof(TSource);
154+
this.DestinationType = typeof(TDestination);
155+
151156
Remove(typeof(TSource), typeof(TDestination));
152157
return ForType<TSource, TDestination>();
153158
}
@@ -161,6 +166,9 @@ public TypeAdapterSetter<TSource, TDestination> NewConfig<TSource, TDestination>
161166
/// <returns></returns>
162167
public TypeAdapterSetter NewConfig(Type sourceType, Type destinationType)
163168
{
169+
this.SourceType = sourceType;
170+
this.DestinationType = destinationType;
171+
164172
Remove(sourceType, destinationType);
165173
return ForType(sourceType, destinationType);
166174
}
@@ -174,6 +182,9 @@ public TypeAdapterSetter NewConfig(Type sourceType, Type destinationType)
174182
/// <returns></returns>
175183
public TypeAdapterSetter<TSource, TDestination> ForType<TSource, TDestination>()
176184
{
185+
this.SourceType = typeof(TSource);
186+
this.DestinationType = typeof(TDestination);
187+
177188
var key = new TypeTuple(typeof(TSource), typeof(TDestination));
178189
var settings = GetSettings(key);
179190
return new TypeAdapterSetter<TSource, TDestination>(settings, this);
@@ -188,6 +199,9 @@ public TypeAdapterSetter<TSource, TDestination> ForType<TSource, TDestination>()
188199
/// <returns></returns>
189200
public TypeAdapterSetter ForType(Type sourceType, Type destinationType)
190201
{
202+
this.SourceType = sourceType;
203+
this.DestinationType = destinationType;
204+
191205
var key = new TypeTuple(sourceType, destinationType);
192206
var settings = GetSettings(key);
193207
return new TypeAdapterSetter(settings, this);
@@ -201,6 +215,9 @@ public TypeAdapterSetter ForType(Type sourceType, Type destinationType)
201215
/// <returns></returns>
202216
public TypeAdapterSetter<TDestination> ForDestinationType<TDestination>()
203217
{
218+
this.SourceType = typeof(void);
219+
this.DestinationType = typeof(TDestination);
220+
204221
var key = new TypeTuple(typeof(void), typeof(TDestination));
205222
var settings = GetSettings(key);
206223
return new TypeAdapterSetter<TDestination>(settings, this);
@@ -214,6 +231,9 @@ public TypeAdapterSetter<TDestination> ForDestinationType<TDestination>()
214231
/// <returns></returns>
215232
public TypeAdapterSetter ForDestinationType(Type destinationType)
216233
{
234+
this.SourceType = typeof(void);
235+
this.DestinationType = destinationType;
236+
217237
var key = new TypeTuple(typeof(void), destinationType);
218238
var settings = GetSettings(key);
219239
return new TypeAdapterSetter(settings, this);

src/Mapster/TypeAdapterSetter.cs

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,30 @@ public static TSetter UseDestinationValue<TSetter>(this TSetter setter, Func<IMe
269269
return setter;
270270
}
271271

272-
internal static TSetter Include<TSetter>(this TSetter setter, Type sourceType, Type destType) where TSetter : TypeAdapterSetter
272+
public static TSetter Include<TSetter>(this TSetter setter, Type sourceType, Type destType) where TSetter : TypeAdapterSetter
273273
{
274274
setter.CheckCompiled();
275275

276+
Type baseSourceType = setter.Config.SourceType;
277+
Type baseDestinationType = setter.Config.DestinationType;
278+
279+
if (baseSourceType.IsOpenGenericType() && baseDestinationType.IsOpenGenericType())
280+
{
281+
if (!sourceType.IsAssignableToGenericType(baseSourceType))
282+
throw new InvalidCastException("In order to use inherits, TSource must be inherited from TBaseSource.");
283+
if (!destType.IsAssignableToGenericType(baseDestinationType))
284+
throw new InvalidCastException("In order to use inherits, TDestination must be inherited from TBaseDestination.");
285+
}
286+
else
287+
{
288+
if (!baseSourceType.GetTypeInfo().IsAssignableFrom(sourceType.GetTypeInfo()))
289+
throw new InvalidCastException("In order to use inherits, TSource must be inherited from TBaseSource.");
290+
291+
if (!baseDestinationType.GetTypeInfo().IsAssignableFrom(destType.GetTypeInfo()))
292+
throw new InvalidCastException("In order to use inherits, TDestination must be inherited from TBaseDestination.");
293+
}
294+
295+
276296
setter.Config.Rules.LockAdd(new TypeAdapterRule
277297
{
278298
Priority = arg =>
@@ -286,6 +306,36 @@ internal static TSetter Include<TSetter>(this TSetter setter, Type sourceType, T
286306
return setter;
287307
}
288308

309+
public static TSetter Inherits<TSetter>(this TSetter setter, Type baseSourceType, Type baseDestinationType) where TSetter : TypeAdapterSetter
310+
{
311+
setter.CheckCompiled();
312+
313+
Type derivedSourceType = setter.Config.SourceType;
314+
Type derivedDestinationType = setter.Config.DestinationType;
315+
316+
if(baseSourceType.IsOpenGenericType() && baseDestinationType.IsOpenGenericType())
317+
{
318+
if (!derivedSourceType.IsAssignableToGenericType(baseSourceType))
319+
throw new InvalidCastException("In order to use inherits, TSource must be inherited from TBaseSource.");
320+
if (!derivedDestinationType.IsAssignableToGenericType(baseDestinationType))
321+
throw new InvalidCastException("In order to use inherits, TDestination must be inherited from TBaseDestination.");
322+
}
323+
else
324+
{
325+
if (!baseSourceType.GetTypeInfo().IsAssignableFrom(derivedSourceType.GetTypeInfo()))
326+
throw new InvalidCastException("In order to use inherits, TSource must be inherited from TBaseSource.");
327+
328+
if (!baseDestinationType.GetTypeInfo().IsAssignableFrom(derivedDestinationType.GetTypeInfo()))
329+
throw new InvalidCastException("In order to use inherits, TDestination must be inherited from TBaseDestination.");
330+
}
331+
332+
if (setter.Config.RuleMap.TryGetValue(new TypeTuple(baseSourceType, baseDestinationType), out var rule))
333+
{
334+
setter.Settings.Apply(rule.Settings);
335+
}
336+
return setter;
337+
}
338+
289339
public static TSetter ApplyAdaptAttribute<TSetter>(this TSetter setter, BaseAdaptAttribute attr) where TSetter : TypeAdapterSetter
290340
{
291341
if (attr.IgnoreAttributes != null)
@@ -812,20 +862,8 @@ public TypeAdapterSetter<TSource, TDestination> Inherits<TBaseSource, TBaseDesti
812862
{
813863
this.CheckCompiled();
814864

815-
Type baseSourceType = typeof(TBaseSource);
816-
Type baseDestinationType = typeof(TBaseDestination);
817-
818-
if (!baseSourceType.GetTypeInfo().IsAssignableFrom(typeof(TSource).GetTypeInfo()))
819-
throw new InvalidCastException("In order to use inherits, TSource must be inherited from TBaseSource.");
820-
821-
if (!baseDestinationType.GetTypeInfo().IsAssignableFrom(typeof(TDestination).GetTypeInfo()))
822-
throw new InvalidCastException("In order to use inherits, TDestination must be inherited from TBaseDestination.");
865+
return this.Inherits(typeof(TBaseSource), typeof(TBaseDestination));
823866

824-
if (Config.RuleMap.TryGetValue(new TypeTuple(baseSourceType, baseDestinationType), out var rule))
825-
{
826-
Settings.Apply(rule.Settings);
827-
}
828-
return this;
829867
}
830868

831869
public TypeAdapterSetter<TSource, TDestination> Fork(Action<TypeAdapterConfig> action)

src/Mapster/Utils/ReflectionUtils.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,5 +407,24 @@ public static bool IsInitOnly(this PropertyInfo propertyInfo)
407407
var isExternalInitType = typeof(System.Runtime.CompilerServices.IsExternalInit);
408408
return setMethod.ReturnParameter.GetRequiredCustomModifiers().Contains(isExternalInitType);
409409
}
410+
411+
public static bool IsAssignableToGenericType(this Type derivedType, Type genericType)
412+
{
413+
414+
if (derivedType.IsGenericType && derivedType.BaseType.GUID == genericType.GUID)
415+
return true;
416+
417+
Type baseType = derivedType.BaseType;
418+
if (baseType == null) return false;
419+
420+
return IsAssignableToGenericType(baseType, genericType);
421+
}
422+
public static bool IsOpenGenericType(this Type type)
423+
{
424+
if(type.IsGenericType)
425+
return type.GetGenericArguments().All(x=>x.GUID == Guid.Empty);
426+
427+
return false;
428+
}
410429
}
411430
}

0 commit comments

Comments
 (0)