这样就可以从模型映射到mvvmcross视图模型,在这里您可以使用一些容器类,例如:
namespace MvvmCross.Binding
{
interface INC<T>
{
T Value { get; set; }
}
}
class model
{
String name { get; set; }
DateTime when { get; set; }
othermodel data { get; set; }
}
class viewmodel
{
INC<String> Name { get; set; }
INC<String> When { get; set; }
INC<otherviewmodel> Data { get; set; }
}
我需要教AutoMapper从A
映射到INC<B>
(并返回),而不指定A或B.
应创建空目标INC<>
,不应重新创建非空。
映射应继续,以便destination.Value = Mapper.Map<A,B>(source)
。
映射null --> INC<T>
应该会产生INC<T>.Value = SomeDefaultValue
答案 0 :(得分:0)
感谢Lucians帮助评论。
这些映射器适用于Mapper.Map<INC<String>>("test")
,但不适用于Mapper.Map<String, INC<String>>(null, new NC<String>("I shouldnt be here"))
,因为AutoMapper不会发送空源值。
Mapper.Initialize(c =>
{
c.Mappers.Insert(0, new DestinationObjectContainerMapper());
c.Mappers.Insert(0, new SourceObjectContainerMapper());
});
Mapper.AssertConfigurationIsValid();
public class DestinationObjectContainerMapper : BlackBoxObjectMapper
{
bool InterfaceMatch(Type x) => x.IsGenericType && typeof(INC<>).IsAssignableFrom(x.GetGenericTypeDefinition());
Type CType(Type cdt) => cdt.GetInterfaces().Concat(new[] { cdt }).Where(InterfaceMatch).Select(x => x.GenericTypeArguments[0]).FirstOrDefault();
public override bool IsMatch(TypePair context) => CType(context.DestinationType) != null;
public override object Map(object source, Type sourceType, object destination, Type destinationType, ResolutionContext context)
{
var dcType = CType(destinationType);
// Create a container if destination is null
if (destination == null)
destination = Activator.CreateInstance(typeof(NC<>).MakeGenericType(dcType));
// This may also fail because we need the source type
var setter = typeof(INC<>).MakeGenericType(dcType).GetProperty("Value").GetSetMethod();
var mappedSource = context.Mapper.Map(source, sourceType, dcType);
// set the value
setter.Invoke(destination, new[] { mappedSource });
return destination;
}
}
public class SourceObjectContainerMapper : BlackBoxObjectMapper
{
bool InterfaceMatch(Type x) => x.IsGenericType && typeof(INC<>).IsAssignableFrom(x.GetGenericTypeDefinition());
Type CType(Type cdt) => cdt.GetInterfaces().Concat(new[] { cdt }).Where(InterfaceMatch).Select(x => x.GenericTypeArguments[0]).FirstOrDefault();
public override bool IsMatch(TypePair context) => CType(context.SourceType) != null;
public override object Map(object source, Type sourceType, object destination, Type destinationType, ResolutionContext context)
{
// this is only obtainable if destination is not null - so this will not work.
var scType = CType(sourceType);
// destination could also be null, this is another anavoidable throw
object sourceContainedValue = null;
if (source != null)
{
var getter = typeof(INC<>).MakeGenericType(scType).GetProperty("Value").GetGetMethod();
sourceContainedValue = getter.Invoke(source, new Object[0]);
}
// map and return
return context.Mapper.Map(sourceContainedValue, scType, destinationType);
}
}
这使用稍微扩展的ObjectMapper:
public abstract class BlackBoxObjectMapper : IObjectMapper
{
private static readonly MethodInfo MapMethod = typeof(BlackBoxObjectMapper).GetTypeInfo().GetDeclaredMethod("Map");
public abstract bool IsMatch(TypePair context);
public abstract object Map(object source, Type sourceType, object destination, Type destinationType, ResolutionContext context);
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
Expression contextExpression) =>
Expression.Call(
Expression.Constant(this),
MapMethod,
sourceExpression,
Expression.Constant(sourceExpression.Type),
destExpression,
Expression.Constant(destExpression.Type),
contextExpression);
}
答案 1 :(得分:0)
感谢Lucians帮助评论。这个使用表达式的映射器有效,并且会根据需要通过this patch和IObjectMapperInfo.CanMapNullSource = true
的一些帮助来接收空值。
AM团队花了很多精力避免传递对Mappers求值为null的表达式,并且它肯定简化了AutoMapper.Mappers命名空间,所以这个补丁显然是有争议的。我想到了一些不那么具有侵入性的方式来表示这个像NullSafeExpression
,或者像interface ICanHandleNullSourceExpressions {}
这样的匿名接口名字来试图阻止流程,但我找不到任何看起来更好的东西。
public class ContainerDestinationMapper : BaseContainerMapper, IObjectMapperInfo
{
readonly Func<Type, Expression> createContainer;
public bool CanMapNullSource => true;
public ContainerDestinationMapper(Type GenericContainerTypeDefinition, String ContainerPropertyName, Func<Type, Expression> createContainer)
: base(GenericContainerTypeDefinition, ContainerPropertyName)
{
this.createContainer = createContainer;
}
public bool IsMatch(TypePair context) => ContainerDef(context.DestinationType) != null;
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
Expression contextExpression)
{
var dparam = DigParameter(destExpression);
var dVal = Expression.Property(dparam, dparam.Type.GetTypeInfo().GetDeclaredProperty("Value"));
var cdt = ContainerDef(destExpression.Type);
var tp = new TypePair(sourceExpression.Type, cdt);
var ret = Expression.Block
(
// make destination not null
Expression.IfThen
(
Expression.Equal(dparam, Expression.Constant(null)),
Expression.Assign(dparam, createContainer(cdt))
),
// Assign to the destination
Expression.Assign
(
dVal,
ExpressionBuilder.MapExpression // what you get if you map source to dest.Value
(
configurationProvider,
profileMap,
tp,
sourceExpression,
contextExpression,
null,
dVal,
true
)
),
destExpression
// But we need to return the destination type!
// Sadly it will go on to assign destExpression to destExpression.
);
return ret;
}
public TypePair GetAssociatedTypes(TypePair initialTypes)
{
return new TypePair(initialTypes.SourceType, ContainerDef(initialTypes.DestinationType));
}
}
public class ContainerSourceMapper : BaseContainerMapper, IObjectMapperInfo
{
public bool CanMapNullSource => true;
public ContainerSourceMapper(Type GenericContainerTypeDefinition, String ContainerPropertyName)
: base(GenericContainerTypeDefinition, ContainerPropertyName) { }
public bool IsMatch(TypePair context) => ContainerDef(context.SourceType) != null;
public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
Expression contextExpression)
{
var dstParam = DigParameter(destExpression);
return Expression.Block(
Expression.IfThenElse
(
Expression.Equal(sourceExpression, Expression.Constant(null)),
Expression.Assign(dstParam, Expression.Default(destExpression.Type)),
Expression.Assign(dstParam,
ExpressionBuilder.MapExpression(configurationProvider, profileMap,
new TypePair(ContainerDef(sourceExpression.Type), destExpression.Type),
Expression.Property(sourceExpression, sourceExpression.Type.GetTypeInfo().GetDeclaredProperty(containerPropertyName)),
contextExpression,
propertyMap,
destExpression
)
)
),
dstParam
);
}
public TypePair GetAssociatedTypes(TypePair initialTypes)
{
return new TypePair(ContainerDef(initialTypes.SourceType), initialTypes.DestinationType);
}
}
public class BaseContainerMapper
{
protected readonly Type genericContainerTypeDefinition;
protected readonly String containerPropertyName;
public BaseContainerMapper(Type GenericContainerTypeDefinition, String ContainerPropertyName)
{
genericContainerTypeDefinition = GenericContainerTypeDefinition;
containerPropertyName = ContainerPropertyName;
}
protected ParameterExpression DigParameter(Expression e)
{
if (e is ParameterExpression pe) return pe;
if (e is UnaryExpression ue) return DigParameter(ue.Operand);
throw new ArgumentException("Couldn't find parameter");
}
public static Type ContainerDef(Type gen, Type to)
{
return new[] { to }.Concat(to.GetInterfaces())
.Where(x => x.IsGenericType)
.Where(x => gen.IsAssignableFrom(x.GetGenericTypeDefinition()))
.Select(x => x.GenericTypeArguments.Single())
.FirstOrDefault(); // Hopefully not overloaded!
}
protected Type ContainerDef(Type to)
{
return ContainerDef(genericContainerTypeDefinition, to);
}
protected PropertyInfo Of(Expression expr)
{
return expr.Type.GetTypeInfo().GetDeclaredProperty(containerPropertyName);
}
}
答案 2 :(得分:0)
This patch允许在typeof(void)
中使用CreateMap
来表示未包含在某个类型中的单个参数(即它将匹配任何类型)并构造ITypeConverter
就这样。
未修补的AutoMapper的替代方法是将X
和Y
替换为以下转换器中的object
。我已经将注释添加到了失败的行。即使忽略了这些失败,这种尝试也需要反思,因此更复杂,效率更低。
Mapper.Initialise(cfg =>
{
cfg.CreateMap(typeof(void), typeof(IContainer<>)).ConvertUsing(typeof(TCf<,>));
cfg.CreateMap(typeof(IContainer<>), typeof(void)).ConvertUsing(typeof(TCb<,>));
});
class TCf<X, Y> : ITypeConverter<X, IContainer<Y>>
{
public IContainer<Y> Convert(X source, IContainer<Y> destination, ResolutionContext context)
{
if (destination == null)
{
// if Y was object we could not create the correct container type
destination = new Container<Y>();
destination.Configure();
}
// if Y was object and source was null, we could not map to the correct type
destination.Value = context.Mapper.Map<Y>(source);
return destination;
}
}
class TCb<X, Y> : ITypeConverter<IContainer<X>, Y>
{
public Y Convert(IContainer<X> source, Y destination, ResolutionContext context)
{
// if X was object and source was null, we could not choose an appropriate default
var use = source == null ? GetSomeDefault<X>() : source.Value;
// if Y was object and destination was null, we could not map to the correct type
return context.Mapper.Map<Y>(use);
}
}
答案 3 :(得分:0)
通过使用ForAllMaps
,您可以获取源/目标类型并提供封闭的,完全通用的转换器类型。如果您想直接致电 - Map<X,IContainer<Y>
,这无济于事,但您不应该这样做。
Mapper.Initialize(c =>
{
c.CreateMap<model, viewmodel>().ReverseMap();
c.ForAllMaps((p, mc) =>
{
Type st = p.SourceType, sct = GetContained(st);
Type dt = p.DestinationType, dct = GetContained(dt);
if (sct != null) mc.ConvertUsing(typeof(TCReverse<,>).MakeGenericType(sct, dt));
if (dct != null) mc.ConvertUsing(typeof(TCForward<,>).MakeGenericType(st, dct));
});
});
Mapper.AssertConfigurationIsValid();
Mapper.Map<viewmodel>(new model());
Mapper.Map<model>(new viewmodel());
使用简单的转换器:
public class TCReverse<X,Y> : ITypeConverter<IContainer<X>, Y>
{
public Y Convert(IContainer<X> source, Y destination, ResolutionContext context)
{
var use = source == null ? default(X) : source.Value;
return context.Mapper.Map(use, destination);
}
}
public class TCForward<X,Y> : ITypeConverter<X, IContainer<Y>>
{
public IContainer<Y> Convert(X source, IContainer<Y> destination, ResolutionContext context)
{
if (destination == null)
destination = new Container<Y>();
destination.Value = context.Mapper.Map(source, destination.Value);
return destination;
}
}
我在这里使用了一个辅助方法:
Type GetContained(Type t)
{
return t.GetInterfaces()
.Concat(new[] { t })
.Where(x => x.IsGenericType)
.Where(x => typeof(IContainer<>).IsAssignableFrom(x.GetGenericTypeDefinition()))
.Select(x => x.GenericTypeArguments[0])
.FirstOrDefault();
}