利用源生成器,在編譯階段生成對映程式碼,減少執行時反射

MTiter發表於2024-08-23

利用源生成器,在編譯階段生成對映程式碼,減少執行時反射

這裡有一個Product類和ProductDto類,實現物件自身的複製,或者Product對映ProductDto

GenMapperAttribute標註了型別需要生成對映方法,同時要求實現IAutoMap介面(由生成器自動實現)

建構函式可選引數為目標型別,預設是自身

MaoToAttributeMapFromAttribute用於自定義轉換動作,顧名思義,MapTo是用於當前型別轉換到目標型別用的,MapFrom是在目標型別標註的,由當前型別呼叫

public interface IAutoMap
{
    object MapTo(string? target = null);
}
[GenMapper]
[GenMapper(typeof(ProductDto))]
internal partial class Product : IAutoMap
{
    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 
    {
        [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;
            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標註,欄位匹配規則也還沒設定

感興趣的可以看看原始碼生成器原始碼地址

相關文章