利用源生成器实现对象映射,在编译阶段生成映射代码,减少运行时反射

利用源生成器,在编译阶段生成映射代码,减少运行时反射

这里有一个Product类和ProductDto类,实现对象自身的拷贝,或者Product映射ProductDto

GenMapperAttribute标注了类型需要生成映射方法,同时实现IAutoMap接口(由生成器实现接口, 类型转换时可以用obj is IAutoMap map检查)

构造函数可选参数为目标类型,默认是自身

MaoToAttributeMapFromAttribute用于自定义转换动作,顾名思义,MapTo是用于当前类型转换到目标类型用的,MapFrom是在目标类型标注的,由当前类型调用

public interface IAutoMap
{
    object MapTo(string? target = null);
}
[GenMapper]
[GenMapper(typeof(ProductDto))]
internal partial class Product
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public string? Category { get; set; }
    [MapTo(Target = typeof(ProductDto), Name = nameof(ProductDto.Date))]
    public DateTime? ProductDate { get; set; }
    public IEnumerable<Product> Products { get; set; } = [];
    
}

internal class ProductDto
{
    public static string NameMapFrom(Product p)
    {
        return $"{p.Category}-{p.Name}";
    }
    public int Id { get; set; }

    [MapFrom(Source = typeof(Product), By = nameof(NameMapFrom))]
    public string? Name { get; set; }
    //[MapFrom(Source = typeof(Product), Name = nameof(Product.ProductDate))]
    public DateTime? Date { get; set; }
}

对于Product类型,生成器将生成如下代码


// <auto-generated/>
#pragma warning disable
namespace TestProject1.Models
{
    [global::System.CodeDom.Compiler.GeneratedCode("AutoGenMapperGenerator.AutoMapperGenerator", "1.0.0.0")]
    /// <inheritdoc/>
    partial class Product : IAutoMap
    {
        [global::System.CodeDom.Compiler.GeneratedCode("AutoGenMapperGenerator.AutoMapperGenerator", "1.0.0.0")]
        public TestProject1.Models.Product MapToProduct()
        {
            var result = new TestProject1.Models.Product();
            result.Id = this.Id;
            result.Name = this.Name;
            result.Category = this.Category;
            result.ProductDate = this.ProductDate;
            result.Products = this.Products.Select(i => i.MapTo<TestProject1.Models.Product>("Product")).ToList();
            return result;
        }

        [global::System.CodeDom.Compiler.GeneratedCode("AutoGenMapperGenerator.AutoMapperGenerator", "1.0.0.0")]
        public TestProject1.Models.ProductDto MapToProductDto()
        {
            var result = new TestProject1.Models.ProductDto();
            result.Date = this.ProductDate;
            result.Id = this.Id;
            result.Name = TestProject1.Models.ProductDto.NameMapFrom(this);
            return result;
        }

        [global::System.CodeDom.Compiler.GeneratedCode("AutoGenMapperGenerator.AutoMapperGenerator", "1.0.0.0")]
        public object? MapTo(string? target = null)
        {
            if (string.IsNullOrEmpty(target))
                throw new ArgumentNullException(nameof(target), "存在多个目标类型,请指定目标类型,推荐使用nameof(TargetType)");
            if (target == nameof(Product))
               return MapToProduct();
            if (target == nameof(ProductDto))
               return MapToProductDto();
            throw new ArgumentException("未找到指定目标类型的映射方法");
        }
    }
}

因此,对于一个已有的Product实例,你可以用如下方法获取Product新实例和ProductDto实例

var p = new Product();
var p1 = p.MapToProduct();
var pdto = p.MapToProductDto();
// 或者使用IAutoMap接口
var p2 = p.MapTo<Product>(nameof(Product));
var dto2 = p.MapTo<ProductDto>(nameof(ProductDto))

总结

源生成器号称编译阶段的反射,性能上确定很有优势。但是目前配置的话确实不方便,需要在字段上进行Attribute标注,字段匹配规则也还没设置

感兴趣的可以看看源码生成器源码地址

posted @ 2024-08-23 10:14  yaoqinglin_mtiter  阅读(14)  评论(0编辑  收藏  举报