Skip to content

Commit ba60290

Browse files
committed
polymorphic implementation for type converters
1 parent 704e5ba commit ba60290

File tree

6 files changed

+70
-43
lines changed

6 files changed

+70
-43
lines changed

src/AutoMapper/Configuration/MappingExpressionBase.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace AutoMapper.Configuration
1010
{
1111
using static Expression;
12+
using static Execution.ExpressionBuilder;
1213
using Execution;
1314
[EditorBrowsable(EditorBrowsableState.Never)]
1415
public interface ITypeMapConfiguration
@@ -456,18 +457,16 @@ public TMappingExpression ConstructUsing(Func<TSource, ResolutionContext, TDesti
456457
public void ConvertUsing(Type typeConverterType)
457458
{
458459
HasTypeConverter = true;
459-
TypeMapActions.Add(tm => tm.TypeConverterType = typeConverterType);
460+
TypeMapActions.Add(tm => tm.TypeConverter = new ClassTypeConverter(Types, typeConverterType));
460461
}
461462

462463
public void ConvertUsing(Func<TSource, TDestination, TDestination> mappingFunction)
463464
{
464465
HasTypeConverter = true;
465466
TypeMapActions.Add(tm =>
466467
{
467-
Expression<Func<TSource, TDestination, ResolutionContext, TDestination>> expr =
468-
(src, dest, ctxt) => mappingFunction(src, dest);
469-
470-
tm.CustomMapFunction = expr;
468+
Expression<Func<TSource, TDestination, ResolutionContext, TDestination>> expr = (src, dest, ctxt) => mappingFunction(src, dest);
469+
tm.TypeConverter = new LambdaTypeConverter(expr);
471470
});
472471
}
473472

@@ -476,10 +475,8 @@ public void ConvertUsing(Func<TSource, TDestination, ResolutionContext, TDestina
476475
HasTypeConverter = true;
477476
TypeMapActions.Add(tm =>
478477
{
479-
Expression<Func<TSource, TDestination, ResolutionContext, TDestination>> expr =
480-
(src, dest, ctxt) => mappingFunction(src, dest, ctxt);
481-
482-
tm.CustomMapFunction = expr;
478+
Expression<Func<TSource, TDestination, ResolutionContext, TDestination>> expr = (src, dest, ctxt) => mappingFunction(src, dest, ctxt);
479+
tm.TypeConverter = new LambdaTypeConverter(expr);
483480
});
484481
}
485482

@@ -488,7 +485,7 @@ public void ConvertUsing(Func<TSource, TDestination, ResolutionContext, TDestina
488485
public void ConvertUsing<TTypeConverter>() where TTypeConverter : ITypeConverter<TSource, TDestination>
489486
{
490487
HasTypeConverter = true;
491-
TypeMapActions.Add(tm => tm.TypeConverterType = typeof(TTypeConverter));
488+
TypeMapActions.Add(tm => tm.TypeConverter = new ClassTypeConverter(Types, typeof(TTypeConverter), typeof(ITypeConverter<TSource, TDestination>)));
492489
}
493490

494491
public TMappingExpression ForCtorParam(string ctorParamName, Action<ICtorParamConfigurationExpression<TSource>> paramOptions)
@@ -525,7 +522,11 @@ public TMappingExpression IgnoreAllSourcePropertiesWithAnInaccessibleSetter()
525522
public void ConvertUsing(Expression<Func<TSource, TDestination>> mappingFunction)
526523
{
527524
HasTypeConverter = true;
528-
TypeMapActions.Add(tm => tm.CustomMapExpression = mappingFunction);
525+
TypeMapActions.Add(tm =>
526+
{
527+
tm.CustomMapExpression = mappingFunction;
528+
tm.TypeConverter = new LambdaTypeConverter(mappingFunction);
529+
});
529530
}
530531

531532
public TMappingExpression AsProxy()

src/AutoMapper/Execution/ExpressionBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public static class ExpressionBuilder
2626
public static readonly MethodInfo IListAdd = typeof(IList).GetMethod(nameof(IList.Add));
2727
public static readonly MethodInfo IncTypeDepthInfo = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.IncrementTypeDepth));
2828
public static readonly MethodInfo DecTypeDepthInfo = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.DecrementTypeDepth));
29-
public static readonly MethodInfo ContextCreate = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.CreateInstance));
29+
private static readonly MethodInfo ContextCreate = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.CreateInstance));
3030
public static readonly MethodInfo OverTypeDepthMethod = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.OverTypeDepth));
3131
public static readonly MethodInfo CacheDestinationMethod = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.CacheDestination));
3232
public static readonly MethodInfo GetDestinationMethod = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.GetDestination));
@@ -133,6 +133,7 @@ Expression DefaultDestination()
133133
return ObjectFactory.GenerateConstructorExpression(destinationType);
134134
}
135135
}
136+
public static Expression ServiceLocator(Type type) => Expression.Call(ContextParameter, ContextCreate, Constant(type));
136137
public static Expression ContextMap(TypePair typePair, Expression sourceParameter, Expression destinationParameter, MemberMap memberMap)
137138
{
138139
var mapMethod = ContextMapMethod.MakeGenericMethod(typePair.SourceType, typePair.DestinationType);

src/AutoMapper/Execution/TypeMapPlanBuilder.cs

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace AutoMapper.Execution
99
using static Expression;
1010
using static ExpressionBuilder;
1111
using Internal;
12+
using AutoMapper.Configuration;
13+
1214
public class TypeMapPlanBuilder
1315
{
1416
private static readonly MethodInfo CreateProxyMethod = typeof(ObjectFactory).GetStaticMethod(nameof(ObjectFactory.CreateInterfaceProxy));
@@ -33,7 +35,7 @@ public TypeMapPlanBuilder(IGlobalConfiguration configurationProvider, TypeMap ty
3335
public LambdaExpression CreateMapperLambda(HashSet<TypeMap> typeMapsPath)
3436
{
3537
var parameters = new[] { Source, _initialDestination, ContextParameter };
36-
var customExpression = TypeConverter(parameters) ?? (_typeMap.CustomMapFunction ?? _typeMap.CustomMapExpression)?.ConvertReplaceParameters(parameters);
38+
var customExpression = _typeMap.TypeConverter?.GetExpression(parameters);
3739
if (customExpression != null)
3840
{
3941
return Lambda(customExpression, parameters);
@@ -61,16 +63,6 @@ public LambdaExpression CreateMapperLambda(HashSet<TypeMap> typeMapsPath)
6163
statements.Add(mapperFunc);
6264
variables.Add(_destination);
6365
return Lambda(Block(variables, statements), parameters);
64-
Expression TypeConverter(ParameterExpression[] parameters)
65-
{
66-
if (_typeMap.TypeConverterType == null)
67-
{
68-
return null;
69-
}
70-
var converterInterfaceType = typeof(ITypeConverter<,>).MakeGenericType(_typeMap.SourceType, DestinationType);
71-
var converter = ServiceLocator(_typeMap.TypeConverterType);
72-
return Call(ToType(converter, converterInterfaceType), "Convert", parameters);
73-
}
7466
static void Clear(ref HashSet<TypeMap> typeMapsPath)
7567
{
7668
if (typeMapsPath == null)
@@ -423,7 +415,6 @@ static Expression CustomMapExpression(Expression mapFrom, Type destinationProper
423415
}
424416
}
425417
private Expression GetCustomSource(MemberMap memberMap) => memberMap.IncludedMember?.Variable ?? Source;
426-
private static Expression ServiceLocator(Type type) => Call(ContextParameter, ContextCreate, Constant(type));
427418
private Expression BuildResolveCall(MemberMap memberMap, Expression source, Expression destValueExpr)
428419
{
429420
var typeMap = memberMap.TypeMap;
@@ -466,4 +457,40 @@ AutoMapperConfigurationException BuildExceptionMessage()
466457
=> new AutoMapperConfigurationException($"Cannot find a source member to pass to the value converter of type {valueConverterConfig.ConcreteType.FullName}. Configure a source member to map from.");
467458
}
468459
}
460+
public abstract class TypeConverter
461+
{
462+
public abstract Expression GetExpression(ParameterExpression[] parameters);
463+
public virtual void CloseGenerics(ITypeMapConfiguration openMapConfig, TypePair closedTypes) { }
464+
}
465+
public class LambdaTypeConverter : TypeConverter
466+
{
467+
public LambdaTypeConverter(LambdaExpression lambda) => Lambda = lambda;
468+
public LambdaExpression Lambda { get; }
469+
public override Expression GetExpression(ParameterExpression[] parameters) => Lambda.ConvertReplaceParameters(parameters);
470+
}
471+
public class ClassTypeConverter : TypeConverter
472+
{
473+
public ClassTypeConverter(TypePair types, Type converterType, Type converterInterface)
474+
{
475+
Types = types;
476+
ConverterType = converterType;
477+
ConverterInterface = converterInterface;
478+
}
479+
public ClassTypeConverter(TypePair types, Type converterType) : this(types, converterType, types.ContainsGenericParameters ? null : types.ITypeConverter())
480+
{
481+
}
482+
public TypePair Types { get; }
483+
public Type ConverterType { get; private set; }
484+
public Type ConverterInterface { get; private set; }
485+
public override Expression GetExpression(ParameterExpression[] parameters) =>
486+
Call(ToType(ServiceLocator(ConverterType), ConverterInterface), "Convert", parameters);
487+
public override void CloseGenerics(ITypeMapConfiguration openMapConfig, TypePair closedTypes)
488+
{
489+
var typeParams = (openMapConfig.SourceType.IsGenericTypeDefinition ? closedTypes.SourceType.GenericTypeArguments : Type.EmptyTypes)
490+
.Concat(openMapConfig.DestinationType.IsGenericTypeDefinition ? closedTypes.DestinationType.GenericTypeArguments : Type.EmptyTypes);
491+
var neededParameters = ConverterType.GenericParametersCount();
492+
ConverterType = ConverterType.MakeGenericType(typeParams.Take(neededParameters).ToArray());
493+
ConverterInterface = closedTypes.ITypeConverter();
494+
}
495+
}
469496
}

src/AutoMapper/Internal/TypePair.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public TypePair CloseGenericTypes(TypePair closedTypes)
5353
var closedDestinationType = DestinationType.IsGenericTypeDefinition ? DestinationType.MakeGenericType(destinationArguments) : DestinationType;
5454
return new TypePair(closedSourceType, closedDestinationType);
5555
}
56+
public Type ITypeConverter() => typeof(ITypeConverter<,>).MakeGenericType(SourceType, DestinationType);
5657
public TypePair GetTypeDefinitionIfGeneric() => new TypePair(GetTypeDefinitionIfGeneric(SourceType), GetTypeDefinitionIfGeneric(DestinationType));
5758
private static Type GetTypeDefinitionIfGeneric(Type type) => type.IsGenericType ? type.GetGenericTypeDefinition() : type;
5859
public static bool operator ==(TypePair left, TypePair right) => left.Equals(right);

src/AutoMapper/ProfileMap.cs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -206,19 +206,8 @@ public TypeMap CreateClosedGenericTypeMap(ITypeMapConfiguration openMapConfig, T
206206
closedMap = new TypeMap(closedTypes.SourceType, closedTypes.DestinationType, this, openMapConfig);
207207
}
208208
openMapConfig.Configure(closedMap);
209-
Configure(closedMap, configurationProvider);
210-
if (closedMap.TypeConverterType != null)
211-
{
212-
var typeParams = (openMapConfig.SourceType.IsGenericTypeDefinition ? closedTypes.SourceType.GenericTypeArguments : Type.EmptyTypes)
213-
.Concat(openMapConfig.DestinationType.IsGenericTypeDefinition ? closedTypes.DestinationType.GenericTypeArguments : Type.EmptyTypes);
214-
var neededParameters = closedMap.TypeConverterType.GenericParametersCount();
215-
closedMap.TypeConverterType = closedMap.TypeConverterType.MakeGenericType(typeParams.Take(neededParameters).ToArray());
216-
}
217-
if (closedMap.DestinationTypeOverride is { IsGenericTypeDefinition: true })
218-
{
219-
var neededParameters = closedMap.DestinationTypeOverride.GenericParametersCount();
220-
closedMap.DestinationTypeOverride = closedMap.DestinationTypeOverride.MakeGenericType(closedTypes.DestinationType.GenericTypeArguments.Take(neededParameters).ToArray());
221-
}
209+
Configure(closedMap, configurationProvider);
210+
closedMap.CloseGenerics(openMapConfig, closedTypes);
222211
return closedMap;
223212
}
224213
public ITypeMapConfiguration GetGenericMap(TypePair genericPair) => _openTypeMapConfigs.GetValueOrDefault(genericPair);

src/AutoMapper/TypeMap.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public TypeMap(Type sourceType, Type destinationType, ProfileMap profile, ITypeM
4848
{
4949
sourceMembers.Clear();
5050
var propertyType = destinationProperty.GetMemberType();
51-
if (profile.MapDestinationPropertyToSource(SourceTypeDetails, destinationType, propertyType, destinationProperty.Name, sourceMembers,
51+
if (profile.MapDestinationPropertyToSource(SourceTypeDetails, destinationType, propertyType, destinationProperty.Name, sourceMembers,
5252
typeMapConfiguration?.IsReverseMap is true))
5353
{
5454
AddPropertyMap(destinationProperty, propertyType, sourceMembers);
@@ -85,12 +85,11 @@ internal bool CanConstructorMap() => Profile.ConstructorMappingEnabled && !Desti
8585
public Type SourceType => Types.SourceType;
8686
public Type DestinationType => Types.DestinationType;
8787
public ProfileMap Profile { get; }
88-
public LambdaExpression CustomMapFunction { get; set; }
8988
public LambdaExpression CustomMapExpression { get; set; }
9089
public LambdaExpression CustomCtorFunction { get; set; }
9190
public LambdaExpression CustomCtorExpression { get; set; }
9291
public Type DestinationTypeOverride
93-
{
92+
{
9493
get => _destinationTypeOverride;
9594
set
9695
{
@@ -109,7 +108,6 @@ public Type DestinationTypeOverride
109108
public IReadOnlyCollection<ValueTransformerConfiguration> ValueTransformers => _valueTransformerConfigs.NullCheck();
110109
public bool PreserveReferences { get; set; }
111110
public int MaxDepth { get; set; }
112-
public Type TypeConverterType { get; set; }
113111
public bool DisableConstructorValidation { get; set; }
114112
public IReadOnlyCollection<PropertyMap> PropertyMaps => _orderedPropertyMaps ?? (_propertyMaps?.Values).NullCheck();
115113
public IReadOnlyCollection<PathMap> PathMaps => (_pathMaps?.Values).NullCheck();
@@ -144,7 +142,8 @@ public IEnumerable<MemberMap> MemberMaps
144142
public ConstructorParameters[] DestinationConstructors => DestinationTypeDetails.Constructors;
145143
public bool ConstructorMapping => ConstructorMap is { CanResolve: true };
146144
public bool CustomConstruction => (CustomCtorExpression ?? CustomCtorFunction) != null;
147-
public bool HasTypeConverter => (CustomMapFunction ?? CustomMapExpression ?? (object)TypeConverterType) != null;
145+
public bool HasTypeConverter => TypeConverter != null;
146+
public TypeConverter TypeConverter { get; set; }
148147
public bool ShouldCheckForValid =>
149148
!HasTypeConverter
150149
&& DestinationTypeOverride == null
@@ -194,7 +193,7 @@ public string[] GetUnmappedPropertyNames()
194193
.Select(p => p.Name)
195194
.Except(MappedMembers().Select(m => m.GetSourceMemberName()))
196195
.Except(IncludedMembersNames)
197-
.Except(IncludedMembers.Select(m=>m.GetMember()?.Name))
196+
.Except(IncludedMembers.Select(m => m.GetMember()?.Name))
198197
.Except(ignoredSourceMembers ?? Array.Empty<string>());
199198
}
200199
return properties.Where(memberName => !Profile.GlobalIgnores.Any(memberName.StartsWith)).ToArray();
@@ -463,5 +462,14 @@ internal void CopyInheritedMapsTo(TypeMap typeMap)
463462
typeMap._inheritedTypeMaps ??= new();
464463
typeMap._inheritedTypeMaps.UnionWith(_inheritedTypeMaps);
465464
}
465+
public void CloseGenerics(ITypeMapConfiguration openMapConfig, TypePair closedTypes)
466+
{
467+
TypeConverter?.CloseGenerics(openMapConfig, closedTypes);
468+
if (DestinationTypeOverride is { IsGenericTypeDefinition: true })
469+
{
470+
var neededParameters = DestinationTypeOverride.GenericParametersCount();
471+
DestinationTypeOverride = DestinationTypeOverride.MakeGenericType(closedTypes.DestinationType.GenericTypeArguments.Take(neededParameters).ToArray());
472+
}
473+
}
466474
}
467475
}

0 commit comments

Comments
 (0)