diff --git a/src/Mapster.Tests/WhenMappingWithOpenGenerics.cs b/src/Mapster.Tests/WhenMappingWithOpenGenerics.cs index bd8f28fe..88cdc66e 100644 --- a/src/Mapster.Tests/WhenMappingWithOpenGenerics.cs +++ b/src/Mapster.Tests/WhenMappingWithOpenGenerics.cs @@ -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 { @@ -36,6 +31,60 @@ public void Setting_From_OpenGeneric_Has_No_SideEffect() var cCopy = c.Adapt(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 { Value = 123 , DerivedValue = 42 }; + var dto = poco.Adapt>(); + 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 { Value = 123, DerivedValue = 42 }; + var dto = poco.Adapt(typeof(GenericPoco<>), typeof(GenericDto<>)); + + dto.ShouldBeOfType>(); + + ((DerivedDto)dto).value.ShouldBe(poco.Value); + ((DerivedDto)dto).derivedValue.ShouldBe(poco.DerivedValue); + } + + public class DerivedPoco : GenericPoco + { + public T DerivedValue { get; set; } + } + + public class DerivedDto : GenericDto + { + public T derivedValue { get; set; } + } + public class GenericPoco { public T Value { get; set; } diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index 366f1eeb..cb689820 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -174,39 +174,47 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de // return adapt(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); diff --git a/src/Mapster/TypeAdapter.cs b/src/Mapster/TypeAdapter.cs index 0ac37924..f30f55fa 100644 --- a/src/Mapster/TypeAdapter.cs +++ b/src/Mapster/TypeAdapter.cs @@ -136,6 +136,12 @@ private static TDestination UpdateFuncFromPackedinObject( /// Adapted destination type. 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); } diff --git a/src/Mapster/TypeAdapterConfig.cs b/src/Mapster/TypeAdapterConfig.cs index cb8ac881..b295db61 100644 --- a/src/Mapster/TypeAdapterConfig.cs +++ b/src/Mapster/TypeAdapterConfig.cs @@ -13,6 +13,8 @@ namespace Mapster { public class TypeAdapterConfig { + public Type SourceType { get; protected set; } + public Type DestinationType { get; protected set; } public static List RulesTemplate { get; } = CreateRuleTemplate(); public static TypeAdapterConfig GlobalSettings { get; } = new TypeAdapterConfig(); @@ -147,6 +149,9 @@ public TypeAdapterSetter When(Func canMap) /// public TypeAdapterSetter NewConfig() { + this.SourceType = typeof(TSource); + this.DestinationType = typeof(TDestination); + Remove(typeof(TSource), typeof(TDestination)); return ForType(); } @@ -160,6 +165,9 @@ public TypeAdapterSetter NewConfig /// public TypeAdapterSetter NewConfig(Type sourceType, Type destinationType) { + this.SourceType = sourceType; + this.DestinationType = destinationType; + Remove(sourceType, destinationType); return ForType(sourceType, destinationType); } @@ -173,6 +181,9 @@ public TypeAdapterSetter NewConfig(Type sourceType, Type destinationType) /// public TypeAdapterSetter ForType() { + this.SourceType = typeof(TSource); + this.DestinationType = typeof(TDestination); + var key = new TypeTuple(typeof(TSource), typeof(TDestination)); var settings = GetSettings(key); return new TypeAdapterSetter(settings, this); @@ -187,6 +198,9 @@ public TypeAdapterSetter ForType() /// 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); @@ -200,6 +214,9 @@ public TypeAdapterSetter ForType(Type sourceType, Type destinationType) /// public TypeAdapterSetter ForDestinationType() { + this.SourceType = typeof(void); + this.DestinationType = typeof(TDestination); + var key = new TypeTuple(typeof(void), typeof(TDestination)); var settings = GetSettings(key); return new TypeAdapterSetter(settings, this); @@ -213,6 +230,9 @@ public TypeAdapterSetter ForDestinationType() /// 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); diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index a0581845..026f7501 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -269,10 +269,30 @@ public static TSetter UseDestinationValue(this TSetter setter, Func(this TSetter setter, Type sourceType, Type destType) where TSetter : TypeAdapterSetter + public static TSetter Include(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 => @@ -286,6 +306,36 @@ internal static TSetter Include(this TSetter setter, Type sourceType, T return setter; } + public static TSetter Inherits(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(this TSetter setter, BaseAdaptAttribute attr) where TSetter : TypeAdapterSetter { if (attr.IgnoreAttributes != null) @@ -812,20 +862,8 @@ public TypeAdapterSetter Inherits Fork(Action action) diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index dae413d8..f9f93759 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -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; + } } } \ No newline at end of file