您可以将 Pawel 的答案和 Reflection.Emit 结合起来,让它变得更快。请注意,并非所有平台(如 iOS)都支持 Reflection.Emit。
与 ExtensionData 不同,这包括源中的所有属性值。我没有一个优雅的解决方案来确定哪些属性已经被映射,所以我只是提供了一种简单的方法来排除某些属性。
public class PropertyDictionaryResolver<TSource> : IValueResolver<TSource, object, Dictionary<string, object>>
{
private static readonly PropertyInfo[] Properties = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
private static readonly ConcurrentDictionary<PropertyInfo, Func<TSource, object>> GetterCache = new ConcurrentDictionary<PropertyInfo, Func<TSource, object>>();
public HashSet<MemberInfo> ExcludedProperties;
public PropertyDictionaryResolver()
{
ExcludedProperties = new HashSet<MemberInfo>();
}
public PropertyDictionaryResolver(Expression<Func<TSource, object>> excludeMembers)
{
var members = ExtractMembers(excludeMembers);
ExcludedProperties = new HashSet<MemberInfo>(members);
}
public Dictionary<string, object> Resolve(TSource source, object destination, Dictionary<string, object> existing, ResolutionContext context)
{
var destMember = new Dictionary<string, object>();
foreach (var property in Properties)
{
if (ExcludedProperties.Contains(property)) continue;
var exp = GetOrCreateExpression(property);
var value = exp(source);
if (value != null)
{
destMember.Add(property.Name, value);
}
}
return destMember;
}
/// <summary>
/// Creates and compiles a getter function for a property
/// </summary>
/// <param name="propInfo"></param>
/// <returns></returns>
public Func<TSource, object> GetOrCreateExpression(PropertyInfo propInfo)
{
if (GetterCache.TryGetValue(propInfo, out var existing))
{
return existing;
}
var parameter = Expression.Parameter(typeof(TSource));
var property = Expression.Property(parameter, propInfo);
var conversion = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda<Func<TSource, object>>(conversion, parameter);
existing = lambda.Compile();
GetterCache.TryAdd(propInfo, existing);
return existing;
}
/// <summary>
/// Pull the used MemberInfo out of a simple expression. Supports the following expression types only:
/// s => s.Prop1
/// s => new { s.Prop1, s.Prop2 }
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression"></param>
/// <returns></returns>
public static IEnumerable<MemberInfo> ExtractMembers<T>(Expression<Func<T, object>> expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
switch (expression.Body)
{
case MemberExpression memberExpression:
yield return memberExpression.Member;
yield break;
// s => s.BarFromBaseType
case UnaryExpression convertExpression:
if (convertExpression.Operand is MemberExpression exp)
{
yield return exp.Member;
}
yield break;
// s => new { s.Foo, s.Bar }
case NewExpression newExpression:
if (newExpression.Arguments.Count == 0)
{
yield break;
}
foreach (var argument in newExpression.Arguments.OfType<MemberExpression>())
{
yield return argument.Member;
}
yield break;
}
throw new NotImplementedException("Unrecognized lambda expression.");
}
}
并像其中之一一样使用它
[TestClass]
public class Examples
{
[TestMethod]
public void AllProperties()
{
var mapper = new Mapper(new MapperConfiguration(p =>
{
p.CreateMap<Source, Destination>()
.ForMember(x => x.A, cfg => cfg.MapFrom(x => x.A))
.ForMember(x => x.D, cfg => cfg.MapFrom<PropertyDictionaryResolver<Source>>());
}));
var source = new Source { A = 1, B = 2, C = 3 };
var d = mapper.Map<Destination>(source);
// {"A":1,"D":{"A":1,"B":2,"C":3}}
}
[TestMethod]
public void ExcludeSingleProperty()
{
var mapper = new Mapper(new MapperConfiguration(p =>
{
p.CreateMap<Source, Destination>()
.ForMember(x => x.A, cfg => cfg.MapFrom(x => x.A))
.ForMember(x => x.D, cfg => cfg.MapFrom(new PropertyDictionaryResolver<Source>(x => x.A)));
}));
var source = new Source { A = 1, B = 2, C = 3 };
var d = mapper.Map<Destination>(source);
// {"A":1,"D":{"B":2,"C":3}}
}
[TestMethod]
public void ExcludeMultipleProperties()
{
var mapper = new Mapper(new MapperConfiguration(p =>
{
p.CreateMap<Source, Destination>()
.ForMember(x => x.A, cfg => cfg.MapFrom(x => x.A))
.ForMember(x => x.D, cfg => cfg.MapFrom(new PropertyDictionaryResolver<Source>(x => new
{
x.A,
x.B
})));
}));
var source = new Source { A = 1, B = 2, C = 3 };
var d = mapper.Map<Destination>(source);
// {"A":1,"D":{"C":3}}
}
}