我需要一种方法将非泛型类型映射到泛型类型以使用
IValueResolver
或 IMemberValueResolver
进行反序列化
问题在于,仅存在使用
IValueResolver
从开放泛型类型到非泛型类型或从开放泛型类型到开放泛型类型的映射。这样就可以按预期工作了。
我需要做的是将泛型类型映射到非泛型类型(有效),反之亦然以进行反序列化。
我的类型是:
public sealed class Foo<T>
{
public T? Value { get; set; }
}
public sealed class SerializableFoo
{
public string? TypeAQN { get; set; }
public string? Data { get; set; }
}
我尝试创建映射:
// Working
this.CreateMap(typeof(Foo<>), typeof(SerializableFoo), MemberList.None)
.ForMember(nameof(SerializableFoo.TypeAQN), opt => opt.MapFrom(typeof(TypeAQNValueResolver<>)))
.ForMember(nameof(SerializableFoo.Data), opt => opt.MapFrom(typeof(DataValueResolver<>)));
// not working
this.CreateMap(typeof(SerializableFoo), typeof(Foo<>), MemberList.None)
.ForMember(nameof(Foo<object>.Value), opt => opt.MapFrom(typeof(SerializableToFooValueResolver<,,,>)));
private sealed class TypeAQNValueResolver<T> : IValueResolver<Foo<T>, SerializableFoo, string?>
{
public string? Resolve(Foo<T> source, SerializableFoo destination, string? destMember, ResolutionContext context) =>
typeof(T).AssemblyQualifiedName;
}
private sealed class DataValueResolver<T> : IValueResolver<Foo<T>, SerializableFoo, string?>
{
/// <inheritdoc />
public string? Resolve(Foo<T> source, SerializableFoo destination, string? destMember, ResolutionContext context) =>
source.Value?.ToString() ?? "<null>";
}
我尝试使用 1、2、3 甚至 4 个泛型创建
IValueResolve
,每次运行时都会出现此异常:
The number of generic arguments provided doesn't equal the arity of the generic type definition.
Parameter name: instantiation
无论我添加多少通用参数。
如何使用
IValueResolver
从非泛型类型映射到开放泛型类型?
编辑
我能够通过引入一个接口来解决这个问题 - 但这感觉是错误的,并且只适用于非常特定的已知泛型参数:
public interface IFoo
{
object? Value { get; set; }
}
public sealed class Foo<T> : IFoo
{
public T? Value { get; set; }
object? IFoo.Value
{
get => this.Value;
set => this.Value = (T?)value;
}
}
映射更改为:
this.CreateMap(typeof(SerializableFoo), typeof(IFoo), MemberList.None)
.IncludeAllDerived()
.ForMember(nameof(IFoo.Value), opt => opt.MapFrom(typeof(SerializableToFooValueResolver))); // Yes - generic CreateMap<,> is now possible.
this.CreateMap(typeof(SerializableFoo), typeof(Foo<>), MemberList.None)
.IncludeBase(typeof(SerializableFoo), typeof(IFoo));
private sealed class SerializableToFooValueResolver : IValueResolver<SerializableFoo, IFoo, object?>
{
/// <inheritdoc />
public object? Resolve(SerializableFoo source, IFoo destination, object? destMember, ResolutionContext context)
{
var destinationType = destination.GetType();
if (destinationType.IsGenericType)
{
var genericArgType = destinationType.GetGenericArguments()[0];
if (genericArgType == typeof(double))
{
return double.Parse(source.Data ?? string.Empty);
}
}
return null;
}
}
这可行,但绝对不是我想要的方式,因为这需要处理很多运行时情况并且感觉不对。
我找到了解决这个问题的方法。如果使用
CreateMap(typeof(..), typeof(Foo<>))
的映射可用,则 AutoMapper 会创建正确的实例。
之后将进行正常的属性映射:
this.CreateMap(typeof(Foo<>), typeof(SerializableFoo), MemberList.None)
.ForMember(nameof(SerializableFoo.TypeAQN), opt => opt.MapFrom(typeof(TypeAQNValueResolver<>)))
.ForMember(nameof(SerializableFoo.Data), opt => opt.MapFrom(typeof(DataValueResolver<>)));
this.CreateMap(typeof(SerializableFoo), typeof(Foo<>), MemberList.None)
.ForMember(nameof(Foo<object>.Value), opt => opt.MapFrom(src => Convert.ChangeType(((SerializableFoo)src).Data, Type.GetType(((SerializableFoo)src).TypeAQN))));
private sealed class TypeAQNValueResolver<T> : IValueResolver<Foo<T>, SerializableFoo, string?>
{
public string? Resolve(Foo<T> source, SerializableFoo destination, string? destMember, ResolutionContext context) =>
typeof(T).AssemblyQualifiedName;
}
private sealed class DataValueResolver<T> : IValueResolver<Foo<T>, SerializableFoo, string?>
{
public string? Resolve(Foo<T> source, SerializableFoo destination, string? destMember, ResolutionContext context) =>
source.Value?.ToString();
}
有了此映射,以下转换将按预期工作:
var config = new MapperConfiguration(cfg => cfg.AddProfile(new CustomProfile()));
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
var foo = new Foo<double> { Value = 123 };
var sFoo = mapper.Map<SerializableFoo>(foo);
Console.WriteLine(sFoo.TypeAQN);
Console.WriteLine(sFoo.Data);
var unFoo = mapper.Map<Foo<int>>(sFoo);
Console.WriteLine(unFoo.Value);
是的,我知道我“反序列化”了一个
Foo<int>
,并且我反序列化了一个double
,AutoMapper 将该 double
转换为预期的 int
,但这就是我所期望的。
有了这个,我就可以创建一个正常的属性映射 - 我只需要确保该属性的转换值与类型匹配或者可以使用 AutoMapper 进行转换。
这也适用于 ctor args。