Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 54 additions & 5 deletions src/Mapster.Tests/WhenMappingWithOpenGenerics.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Mapster.Tests
{
Expand Down Expand Up @@ -36,6 +31,60 @@ public void Setting_From_OpenGeneric_Has_No_SideEffect()
var cCopy = c.Adapt<C>(config);
}

[TestMethod]
public void MapOpenGenericsUseInherits()
{
TypeAdapterConfig.GlobalSettings
.ForType(typeof(GenericPoco<>), typeof(GenericDto<>))
.Map("value", "Value");

TypeAdapterConfig.GlobalSettings
.ForType(typeof(DerivedPoco<>), typeof(DerivedDto<>))
.Map("derivedValue", "DerivedValue")
.Inherits(typeof(GenericPoco<>), typeof(GenericDto<>));

var poco = new DerivedPoco<int> { Value = 123 , DerivedValue = 42 };
var dto = poco.Adapt<DerivedDto<int>>();
dto.value.ShouldBe(poco.Value);
dto.derivedValue.ShouldBe(poco.DerivedValue);
}

[TestMethod]
public void MapOpenGenericsUseInclude()
{
TypeAdapterConfig.GlobalSettings.Clear();

TypeAdapterConfig.GlobalSettings
.ForType(typeof(DerivedPoco<>), typeof(DerivedDto<>))
.Map("derivedValue", "DerivedValue");

TypeAdapterConfig.GlobalSettings
.ForType(typeof(GenericPoco<>), typeof(GenericDto<>))
.Map("value", "Value");

TypeAdapterConfig.GlobalSettings
.ForType(typeof(GenericPoco<>), typeof(GenericDto<>))
.Include(typeof(DerivedPoco<>), typeof(DerivedDto<>));

var poco = new DerivedPoco<int> { Value = 123, DerivedValue = 42 };
var dto = poco.Adapt(typeof(GenericPoco<>), typeof(GenericDto<>));

dto.ShouldBeOfType<DerivedDto<int>>();

((DerivedDto<int>)dto).value.ShouldBe(poco.Value);
((DerivedDto<int>)dto).derivedValue.ShouldBe(poco.DerivedValue);
}

public class DerivedPoco<T> : GenericPoco<T>
{
public T DerivedValue { get; set; }
}

public class DerivedDto<T> : GenericDto<T>
{
public T derivedValue { get; set; }
}

public class GenericPoco<T>
{
public T Value { get; set; }
Expand Down
26 changes: 17 additions & 9 deletions src/Mapster/Adapters/BaseAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,39 +174,47 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de
// return adapt<TSource, TDest>(drvdSource);
foreach (var tuple in arg.Settings.Includes)
{
TypeTuple itemTuple = tuple;

if (tuple.Source.IsOpenGenericType() && tuple.Destination.IsOpenGenericType())
{
var genericArg = source.Type.GetGenericArguments();
itemTuple = new TypeTuple(tuple.Source.MakeGenericType(genericArg), tuple.Destination.MakeGenericType(genericArg));
}

//same type, no redirect to prevent endless loop
if (tuple.Source == arg.SourceType)
if (itemTuple.Source == arg.SourceType)
continue;

//type is not compatible, no redirect
if (!arg.SourceType.GetTypeInfo().IsAssignableFrom(tuple.Source.GetTypeInfo()))
if (!arg.SourceType.GetTypeInfo().IsAssignableFrom(itemTuple.Source.GetTypeInfo()))
continue;

var drvdSource = Expression.Variable(tuple.Source);
var drvdSource = Expression.Variable(itemTuple.Source);
vars.Add(drvdSource);

var drvdSourceAssign = Expression.Assign(
drvdSource,
Expression.TypeAs(source, tuple.Source));
Expression.TypeAs(source, itemTuple.Source));
blocks.Add(drvdSourceAssign);
var cond = Expression.NotEqual(drvdSource, Expression.Constant(null, tuple.Source));
var cond = Expression.NotEqual(drvdSource, Expression.Constant(null, itemTuple.Source));

ParameterExpression? drvdDest = null;
if (destination != null)
{
drvdDest = Expression.Variable(tuple.Destination);
drvdDest = Expression.Variable(itemTuple.Destination);
vars.Add(drvdDest);

var drvdDestAssign = Expression.Assign(
drvdDest,
Expression.TypeAs(destination, tuple.Destination));
Expression.TypeAs(destination, itemTuple.Destination));
blocks.Add(drvdDestAssign);
cond = Expression.AndAlso(
cond,
Expression.NotEqual(drvdDest, Expression.Constant(null, tuple.Destination)));
Expression.NotEqual(drvdDest, Expression.Constant(null, itemTuple.Destination)));
}

var adaptExpr = CreateAdaptExpressionCore(drvdSource, tuple.Destination, arg, destination: drvdDest);
var adaptExpr = CreateAdaptExpressionCore(drvdSource, itemTuple.Destination, arg, destination: drvdDest);
var adapt = Expression.Return(label, adaptExpr);
var ifExpr = Expression.IfThen(cond, adapt);
blocks.Add(ifExpr);
Expand Down
6 changes: 6 additions & 0 deletions src/Mapster/TypeAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ private static TDestination UpdateFuncFromPackedinObject<TSource, TDestination>(
/// <returns>Adapted destination type.</returns>
public static object? Adapt(this object source, Type sourceType, Type destinationType)
{
if (source != null &&
sourceType.IsOpenGenericType() && destinationType.IsOpenGenericType())
{
var arg = source.GetType().GetGenericArguments();
return Adapt(source, sourceType.MakeGenericType(arg), destinationType.MakeGenericType(arg), TypeAdapterConfig.GlobalSettings);
}
return Adapt(source, sourceType, destinationType, TypeAdapterConfig.GlobalSettings);
}

Expand Down
20 changes: 20 additions & 0 deletions src/Mapster/TypeAdapterConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ namespace Mapster
{
public class TypeAdapterConfig
{
public Type SourceType { get; protected set; }
public Type DestinationType { get; protected set; }
public static List<TypeAdapterRule> RulesTemplate { get; } = CreateRuleTemplate();
public static TypeAdapterConfig GlobalSettings { get; } = new TypeAdapterConfig();

Expand Down Expand Up @@ -147,6 +149,9 @@ public TypeAdapterSetter When(Func<PreCompileArgument, bool> canMap)
/// <returns></returns>
public TypeAdapterSetter<TSource, TDestination> NewConfig<TSource, TDestination>()
{
this.SourceType = typeof(TSource);
this.DestinationType = typeof(TDestination);

Remove(typeof(TSource), typeof(TDestination));
return ForType<TSource, TDestination>();
}
Expand All @@ -160,6 +165,9 @@ public TypeAdapterSetter<TSource, TDestination> NewConfig<TSource, TDestination>
/// <returns></returns>
public TypeAdapterSetter NewConfig(Type sourceType, Type destinationType)
{
this.SourceType = sourceType;
this.DestinationType = destinationType;

Remove(sourceType, destinationType);
return ForType(sourceType, destinationType);
}
Expand All @@ -173,6 +181,9 @@ public TypeAdapterSetter NewConfig(Type sourceType, Type destinationType)
/// <returns></returns>
public TypeAdapterSetter<TSource, TDestination> ForType<TSource, TDestination>()
{
this.SourceType = typeof(TSource);
this.DestinationType = typeof(TDestination);

var key = new TypeTuple(typeof(TSource), typeof(TDestination));
var settings = GetSettings(key);
return new TypeAdapterSetter<TSource, TDestination>(settings, this);
Expand All @@ -187,6 +198,9 @@ public TypeAdapterSetter<TSource, TDestination> ForType<TSource, TDestination>()
/// <returns></returns>
public TypeAdapterSetter ForType(Type sourceType, Type destinationType)
{
this.SourceType = sourceType;
this.DestinationType = destinationType;

var key = new TypeTuple(sourceType, destinationType);
var settings = GetSettings(key);
return new TypeAdapterSetter(settings, this);
Expand All @@ -200,6 +214,9 @@ public TypeAdapterSetter ForType(Type sourceType, Type destinationType)
/// <returns></returns>
public TypeAdapterSetter<TDestination> ForDestinationType<TDestination>()
{
this.SourceType = typeof(void);
this.DestinationType = typeof(TDestination);

var key = new TypeTuple(typeof(void), typeof(TDestination));
var settings = GetSettings(key);
return new TypeAdapterSetter<TDestination>(settings, this);
Expand All @@ -213,6 +230,9 @@ public TypeAdapterSetter<TDestination> ForDestinationType<TDestination>()
/// <returns></returns>
public TypeAdapterSetter ForDestinationType(Type destinationType)
{
this.SourceType = typeof(void);
this.DestinationType = destinationType;

var key = new TypeTuple(typeof(void), destinationType);
var settings = GetSettings(key);
return new TypeAdapterSetter(settings, this);
Expand Down
66 changes: 52 additions & 14 deletions src/Mapster/TypeAdapterSetter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,30 @@ public static TSetter UseDestinationValue<TSetter>(this TSetter setter, Func<IMe
return setter;
}

internal static TSetter Include<TSetter>(this TSetter setter, Type sourceType, Type destType) where TSetter : TypeAdapterSetter
public static TSetter Include<TSetter>(this TSetter setter, Type sourceType, Type destType) where TSetter : TypeAdapterSetter
{
setter.CheckCompiled();

Type baseSourceType = setter.Config.SourceType;
Type baseDestinationType = setter.Config.DestinationType;

if (baseSourceType.IsOpenGenericType() && baseDestinationType.IsOpenGenericType())
{
if (!sourceType.IsAssignableToGenericType(baseSourceType))
throw new InvalidCastException("In order to use inherits, TSource must be inherited from TBaseSource.");
if (!destType.IsAssignableToGenericType(baseDestinationType))
throw new InvalidCastException("In order to use inherits, TDestination must be inherited from TBaseDestination.");
}
else
{
if (!baseSourceType.GetTypeInfo().IsAssignableFrom(sourceType.GetTypeInfo()))
throw new InvalidCastException("In order to use inherits, TSource must be inherited from TBaseSource.");

if (!baseDestinationType.GetTypeInfo().IsAssignableFrom(destType.GetTypeInfo()))
throw new InvalidCastException("In order to use inherits, TDestination must be inherited from TBaseDestination.");
}


setter.Config.Rules.LockAdd(new TypeAdapterRule
{
Priority = arg =>
Expand All @@ -286,6 +306,36 @@ internal static TSetter Include<TSetter>(this TSetter setter, Type sourceType, T
return setter;
}

public static TSetter Inherits<TSetter>(this TSetter setter, Type baseSourceType, Type baseDestinationType) where TSetter : TypeAdapterSetter
{
setter.CheckCompiled();

Type derivedSourceType = setter.Config.SourceType;
Type derivedDestinationType = setter.Config.DestinationType;

if(baseSourceType.IsOpenGenericType() && baseDestinationType.IsOpenGenericType())
{
if (!derivedSourceType.IsAssignableToGenericType(baseSourceType))
throw new InvalidCastException("In order to use inherits, TSource must be inherited from TBaseSource.");
if (!derivedDestinationType.IsAssignableToGenericType(baseDestinationType))
throw new InvalidCastException("In order to use inherits, TDestination must be inherited from TBaseDestination.");
}
else
{
if (!baseSourceType.GetTypeInfo().IsAssignableFrom(derivedSourceType.GetTypeInfo()))
throw new InvalidCastException("In order to use inherits, TSource must be inherited from TBaseSource.");

if (!baseDestinationType.GetTypeInfo().IsAssignableFrom(derivedDestinationType.GetTypeInfo()))
throw new InvalidCastException("In order to use inherits, TDestination must be inherited from TBaseDestination.");
}

if (setter.Config.RuleMap.TryGetValue(new TypeTuple(baseSourceType, baseDestinationType), out var rule))
{
setter.Settings.Apply(rule.Settings);
}
return setter;
}

public static TSetter ApplyAdaptAttribute<TSetter>(this TSetter setter, BaseAdaptAttribute attr) where TSetter : TypeAdapterSetter
{
if (attr.IgnoreAttributes != null)
Expand Down Expand Up @@ -812,20 +862,8 @@ public TypeAdapterSetter<TSource, TDestination> Inherits<TBaseSource, TBaseDesti
{
this.CheckCompiled();

Type baseSourceType = typeof(TBaseSource);
Type baseDestinationType = typeof(TBaseDestination);

if (!baseSourceType.GetTypeInfo().IsAssignableFrom(typeof(TSource).GetTypeInfo()))
throw new InvalidCastException("In order to use inherits, TSource must be inherited from TBaseSource.");

if (!baseDestinationType.GetTypeInfo().IsAssignableFrom(typeof(TDestination).GetTypeInfo()))
throw new InvalidCastException("In order to use inherits, TDestination must be inherited from TBaseDestination.");
return this.Inherits(typeof(TBaseSource), typeof(TBaseDestination));

if (Config.RuleMap.TryGetValue(new TypeTuple(baseSourceType, baseDestinationType), out var rule))
{
Settings.Apply(rule.Settings);
}
return this;
}

public TypeAdapterSetter<TSource, TDestination> Fork(Action<TypeAdapterConfig> action)
Expand Down
19 changes: 19 additions & 0 deletions src/Mapster/Utils/ReflectionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,5 +380,24 @@ public static bool IsInitOnly(this PropertyInfo propertyInfo)
var isExternalInitType = typeof(System.Runtime.CompilerServices.IsExternalInit);
return setMethod.ReturnParameter.GetRequiredCustomModifiers().Contains(isExternalInitType);
}

public static bool IsAssignableToGenericType(this Type derivedType, Type genericType)
{

if (derivedType.IsGenericType && derivedType.BaseType.GUID == genericType.GUID)
return true;

Type baseType = derivedType.BaseType;
if (baseType == null) return false;

return IsAssignableToGenericType(baseType, genericType);
}
public static bool IsOpenGenericType(this Type type)
{
if(type.IsGenericType)
return type.GetGenericArguments().All(x=>x.GUID == Guid.Empty);

return false;
}
}
}
Loading