如何创建这样的通用映射器?

Yor*_*oro 3 c# .net-core

我有很多类,它们具有类似的映射对象操作。abstract class为了保持代码干燥,我想为所有这些类创建基础。baseabstract class将具有如下通用映射函数:

public TEntity GetEntity(Result<TServiceClientEntity> res) 
{
    entity = MappingProfiles.TryMap(res.Value);
    //some logic with result here
    return entity;
}
Run Code Online (Sandbox Code Playgroud)

结果类:

public class Result<T>
{
    public T Value{ get; set; }
    //some more properties..
}
Run Code Online (Sandbox Code Playgroud)

但问题是我想不出如何映射通用类的方法:

public static class MappingProfiles
{
    public static T2 TryMap<T,T2>(T t) 
    {
        return (T2)Map((Real_T_type)t); //f.e.: the type is ExampleFrom
    }

    public static ExampleTo Map(ExampleFrom from)
    {
        return new ExampleTo
        {
            exapleValue = from.exapleValue
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:

我还希望TryMap通用方法使用我预定义的Map手动方法进行映射。

Oli*_*bes 5

您可以使用反射 (C#)来完成以下任务:

public static TOut Map<TIn, TOut>(TIn source)
    where TOut : new()
{
    var inPropDict = typeof(TIn).GetProperties()
        .Where(p => p.CanRead)
        .ToDictionary(p => p.Name);
    var outProps = typeof(TOut).GetProperties()
        .Where(p => p.CanWrite);
    var destination = new TOut();
    foreach (var outProp in outProps) {
        if (inPropDict.TryGetValue(outProp.Name, out var inProp)) {
            object sourceValue = inProp.GetValue(source);
            if (inProp.PropertyType != outProp.PropertyType) {
                sourceValue = Convert.ChangeType(sourceValue, outProp.PropertyType);
            }
            outProp.SetValue(destination, sourceValue);
        }
    }
    return destination;
}
Run Code Online (Sandbox Code Playgroud)

反射使您能够检查类型并获取其属性、字段等。

Type.GetProperties()返回一个PropertyInfo包含名称、类型和有关属性的其他信息的数组。它还允许您读取或写入对象的属性。

上面的代码只是一个快速而肮脏的示例,没有异常处理。它仅执行平面映射,不映射集合或嵌套对象。它还可以通过允许您声明不具有相同名称的属性的映射等来改进。

有一个工具可以完成所有这些事情,甚至更多,称为AutoMapper


手动映射方法的解决方案

我建议定义一个这样的接口

public interface IMapper<T1, T2>
{
    T2 Map(T1 input);
}
Run Code Online (Sandbox Code Playgroud)

具体实现示例:

public class ExampleFromToMapper : IMapper<ExampleFrom, ExampleTo>
{
    public ExampleTo Map(ExampleFrom input)
    {
        return new ExampleTo {
            ExampleValue = input.ExampleValue
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

这个想法是使用依赖注入来完成选择正确映射器的工作。

您可以使用 NuGet 包Microsoft.Extensions.DependencyInjection作为示例。但还存在许多其他依赖注入框架。

编写一个配置映射器的方法(作为本例中的扩展方法):

public static IServiceCollection AddMappers(this IServiceCollection services)
{
    return services
        .AddSingleton<IMapper<ExampleFrom, ExampleTo>, ExampleFromToMapper>()
        .AddSingleton<IMapper<OtherFrom, OtherTo>, OtherFromToMapper>();
}
Run Code Online (Sandbox Code Playgroud)

在某处定义容器:

public static class Config
{
    public static ServiceProvider Container { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

并在应用程序启动时配置容器

var services = new ServiceCollection();
services
    .AddMappers()
    .AddTransient<MyForm>(); // See below
Config.Container = services.BuildServiceProvider();
Run Code Online (Sandbox Code Playgroud)

作为一个例子,让我们假设您有一个 WinForms 应用程序,其表单定义如下(它直接使用映射器,但它可以使用其他使用映射器的服务。DI 容器递归地解析依赖项并将它们注入到自动构造函数):

public partial class MyForm : Form
{
    private readonly IMapper<ExampleFrom, ExampleTo> _mapper;

    public MyForm(IMapper<ExampleFrom, ExampleTo> mapper)
    {
        _mapper = mapper;
        InitializeComponent();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以像这样启动应用程序:

var frm = Config.Container.GetRequiredService<MyForm>();
Application.Run(frm);
Run Code Online (Sandbox Code Playgroud)

好吧,一开始看起来很复杂,但是一旦设置了基础知识,添加新服务就变得很容易。每个提供某些功能的类都被视为一项服务。