我意识到我希望映射器的行为Source.Data
是Destination.Data
基于Source.Data
. 具体来说,对于给定的类型,TSource
我希望能够准确定义一个目标类型TDestination
,以便当Source.Data
有类型TSource
时Destination.Data
有类型TDestination
。
Map
or的问题DynamicMap
是您在执行映射时必须知道目标类型,但在配置时您只知道Destination.Data
它的类型object
- 您需要明确说明类型目标类型以及您可以做的唯一方法即通过维护源类型到目标类型的映射。
我在AutoMapper
配置方法上实现了几个扩展方法,允许Map
注册注入并Member
使用注入策略解决:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using AutoMapper;
/// <summary>
/// The <seealso cref="AutoMapper"/> extensions.
/// </summary>
public static class AutoMapperExtensions
{
/// <summary>
/// The injection maps.
/// </summary>
private static readonly SortedDictionary<Type, Type> Injections =
new SortedDictionary<Type, Type>(new TypeComparer());
/// <summary>
/// Registers a mapping expression for injection.
/// </summary>
/// <param name="expression">
/// The expression to register fo injection.
/// </param>
/// <typeparam name="TSource">
/// The source type.
/// </typeparam>
/// <typeparam name="TDestination">
/// The destination type.
/// </typeparam>
/// <returns>
/// The original <see cref="IMappingExpression"/> to allow for fluent chaining.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Thrown when <typeparam name="TSource"/> is an interface or has already been enabled.
/// </exception>
public static IMappingExpression<TSource, TDestination> RegisterForInjection<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression)
where TSource : class
where TDestination : class // These constraints mean that null can be mapped to null.
{
var sourceType = typeof(TSource);
// Interfaces do not have a strict hierarchy so the cannot be registered.
if (sourceType.IsInterface)
{
throw new InvalidOperationException(
string.Format(
"The type {0} is an interface and interface types cannot be registered for injection",
sourceType));
}
// This is important: for injection to work there can be exactly one target.
if (Injections.ContainsKey(sourceType))
{
throw new InvalidOperationException(
string.Format("The type {0} has already been registered for injection", sourceType));
}
Injections.Add(sourceType, typeof(TDestination));
return expression;
}
/// <summary>
/// Instructs the mapper to resolve the destination member using an injection
/// strategy based on the actual type of the source member at runtime.
/// </summary>
/// <param name="expression">
/// The member expression on which to use an injection strategy.
/// </param>
/// <param name="sourceMember">
/// The source member to map from.
/// </param>
/// <typeparam name="TSource">
/// The source type.
/// </typeparam>
/// <typeparam name="TMember">
/// The source member type (probably object).
/// </typeparam>
/// <exception cref="InvalidOperationException">
/// When the actual type of <typeparam name="TMember"/> has not been registered for injection.
/// </exception>
public static void InjectFrom<TSource, TMember>(
this IMemberConfigurationExpression<TSource> expression,
Expression<Func<TSource, TMember>> sourceMember)
where TMember : class
{
var getSourceMember = sourceMember.Compile();
expression.ResolveUsing(
s =>
{
var sourceMemberValue = getSourceMember(s);
if (sourceMemberValue == null)
{
return null;
}
var sourceMemberType = sourceMemberValue.GetType();
Type destinationMemberType = null;
// Because the injections are sorted by number of super classes it is
// guaranteed that the first one that is assignable from the source type
// is the most derived.
foreach (var injection in Injections)
{
if (injection.Key.IsAssignableFrom(sourceMemberType))
{
// {injection.Key} value = default({sourceMemberType}) compiles.
destinationMemberType = injection.Value;
break;
}
}
if (destinationMemberType == null)
{
throw new InvalidOperationException(
string.Format(
"The type {0} has not been enabled for injection.",
sourceMemberType));
}
return Mapper.Map(sourceMemberValue, sourceMemberType, destinationMemberType);
});
}
/// <summary>
/// The type comparer.
/// </summary>
private class TypeComparer : IComparer<Type>
{
/// <summary>
/// Compares <paramref name="x"/> with <paramref name="y"/>.
/// </summary>
/// <param name="x">
/// The left hand type.
/// </param>
/// <param name="y">
/// The right hand type
/// </param>
/// <returns>
/// The difference in the number of super classes between y and x, specifically:
/// x < y if x has more super classes than y
/// </returns>
public int Compare(Type x, Type y)
{
return CountSuperClasses(y) - CountSuperClasses(x);
}
/// <summary>
/// Counts the number of super classes beneath <paramref name="type"/>.
/// </summary>
/// <param name="type">
/// The type.
/// </param>
/// <returns>
/// The number of super classes beneath <paramref name="type"/>.
/// </returns>
private static int CountSuperClasses(Type type)
{
var count = 0;
while (type.BaseType != null)
{
++count;
type = type.BaseType;
}
return count;
}
}
}