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

3

我有很多类,它们具有映射对象的相似操作。为了保持代码的DRY(不要重复自己),我想为所有这些类创建一个基础的抽象类。 基础抽象类将具有以下通用映射函数:

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

结果类:

public class Result<T>
{
    public T Value{ get; set; }
    //some more properties..
}

但问题在于,我无法想出如何映射这样的通用类:

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
        };
    }
}

编辑:

我还希望TryMap泛型方法使用我预定义的Map手动映射方法。


1
你可以使用 AutoMapper,并且检查 GitHub 上的开源代码 - Olivier Jacot-Descombes
2个回答

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;
}

Reflection使您能够检查类型并获取其属性、字段等信息。
`Type.GetProperties()`返回一个`PropertyInfo`数组,其中包含有关属性的名称、类型和其他信息。它还允许您从对象中读取或写入属性。
上面的代码只是一个快速而简单的示例,没有异常处理。它仅进行浅层映射,并不映射集合或嵌套对象。同时,它也可以通过允许您声明未具有相同名称的属性的映射来进行改进。
有一种工具可以完成所有这些事情及更多,它叫做AutoMapper

使用手动映射方法的解决方案

我建议定义一个如下的接口

public interface IMapper<T1, T2>
{
    T2 Map(T1 input);
}

具体实现示例:

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

这个想法是使用依赖注入来选择正确的映射器。
你可以使用NuGet包Microsoft.Extensions.DependencyInjection作为示例。但是还有许多其他的依赖注入框架存在。
编写一个配置映射器的方法(在此示例中作为扩展方法):
public static IServiceCollection AddMappers(this IServiceCollection services)
{
    return services
        .AddSingleton<IMapper<ExampleFrom, ExampleTo>, ExampleFromToMapper>()
        .AddSingleton<IMapper<OtherFrom, OtherTo>, OtherFromToMapper>();
}

在某处定义容器:
public static class Config
{
    public static ServiceProvider Container { get; set; }
}

在应用程序启动时配置容器。
var services = new ServiceCollection();
services
    .AddMappers()
    .AddTransient<MyForm>(); // See below
Config.Container = services.BuildServiceProvider();

作为一个例子,假设您有一个WinForms应用程序,其中一个表单被定义为这样(它直接使用映射器,但也可以使用其他使用映射器的服务。DI容器递归解析依赖项并自动将它们注入构造函数):
public partial class MyForm : Form
{
    private readonly IMapper<ExampleFrom, ExampleTo> _mapper;

    public MyForm(IMapper<ExampleFrom, ExampleTo> mapper)
    {
        _mapper = mapper;
        InitializeComponent();
    }
}

现在,您可以这样启动应用程序:
var frm = Config.Container.GetRequiredService<MyForm>();
Application.Run(frm);

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


很好的回答!在我的情况下,我的基类不知道 ExampleFrom 类型。因此,我修改了您的代码,将 _mapper 注入到 GetEntity 方法本身中。 if(_mapper is null) _mapper = (IMapper<ExampleFrom, ExampleTo>)service.GetService(typeof(IMapper<ExampleFrom, ExampleTo>)) 并且基类拥有这个私有属性 private object? _mapper = null - Yoro

0

使用手动静态映射方法的解决方案。

为了使类更容易进行测试(或出于其他原因),通用映射器也可以是静态的。 感谢Oliver Jacot-Descombes的回答,我创建了这个解决方案:

//This works like static class.
public class MappingProfiles
{
    //Constructor needs to be private, so that class work like static class.
    private MappingProfiles() { }
    private static readonly MappingProfiles _instance = new MappingProfiles();
    //We need instance to invoke this class methods.
    public static MappingProfiles Instance { get { return _instance; } }

    public static TResult? TryMap<TSource, TResult>(TSource source)
    where TResult : new()
    where TSource : class, new()
    {
        //Trying to get mapper for result.
        var resultMapper = typeof(MappingProfiles).GetMethods().FirstOrDefault(m =>
            m.ReturnType == typeof(TResult) &&
            m.GetParameters()[0].ParameterType == typeof(TSource));
        if (resultMapper is null)
            throw new Exception($"Mapper not found. From: {typeof(TSource)}, to: {typeof(TResult)}");

        //Makes TSource array, because Invoke method works only with arrays.
        TSource[] sources = new TSource[] { source };

        //Calls manual mapping method.
        object res = resultMapper.Invoke(MappingProfiles.Instance, sources);

        return (TResult)res;
    }

    public static ExampleTo Map(ExampleFrom from)
    {
        return new ExampleTo
        {
            //...
        };
    }
}

调用此映射器:

var mapped = MappingProfiles.TryMap<TExampleFrom, TExampleTo>(exampleFromVariable);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接