如何提高 C# 对象映射代码的性能
How to Improve Performance of C# Object Mapping Code
如何在保持 public 界面的同时进一步提高下面代码的性能:
public interface IMapper<in TSource, in TDestination>
{
void Map(TSource source, TDestination destination);
}
public static TDestination Map<TSource, TDestination>(
this IMapper<TSource, TDestination> translator,
TSource source)
where TDestination : new()
{
var destination = new TDestination();
translator.Map(source, destination);
return destination;
}
public static List<TDestination> MapList<TSource, TDestination>(
this IMapper<TSource, TDestination> translator,
List<TSource> source)
where TDestination : new()
{
var destinationCollection = new List<TDestination>(source.Count);
foreach (var sourceItem in source)
{
var destinationItem = translator.Map(sourceItem);
destinationCollection.Add(destinationItem);
}
return destinationCollection;
}
用法示例
public class MapFrom { public string Property { get; set; } }
public class MapTo { public string Property { get; set; } }
public class Mapper : IMapper<MapFrom, MapTo>
{
public void Map(MapFrom source, MapTo destination)
{
destination.Property = source.Property;
}
}
var mapper = new Mapper();
var mapTo = mapper.Map(new MapFrom() { Property = "Foo" });
var mapToList = mapper.MapList(
new List<MapFrom>()
{
new MapFrom() { Property = "Foo" }
});
当前基准
当我 运行 针对原始手动转换的基准时,这些是我得到的数字:
| Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Scaled | ScaledSD | Gen 0 | Allocated |
|------------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|-------:|---------:|-------:|----------:|
| Baseline | Clr | Clr | 1.969 us | 0.0354 us | 0.0332 us | 1.927 us | 2.027 us | 1.00 | 0.00 | 2.0523 | 6.31 KB |
| Mapper | Clr | Clr | 9.016 us | 0.1753 us | 0.2019 us | 8.545 us | 9.419 us | 4.58 | 0.12 | 2.0447 | 6.31 KB |
| Baseline | Core | Core | 1.820 us | 0.0346 us | 0.0355 us | 1.777 us | 1.902 us | 1.00 | 0.00 | 2.0542 | 6.31 KB |
| Mapper | Core | Core | 9.043 us | 0.1725 us | 0.1613 us | 8.764 us | 9.294 us | 4.97 | 0.13 | 2.0447 | 6.31 KB |
这里是基线代码:
var mapTo = new MapTo() { Property = mapFrom.Property };
var mapToCollection = new List<MapTo>(this.mapFrom.Count);
foreach (var item in this.mapFrom)
{
destination.Add(new MapTo() { Property = item.Property });
}
基准代码
我在 Dotnet-Boxed/Framework GitHub 存储库中有一个包含映射器和 Benchmark.NET 项目的完整工作项目。
在实施评论中讨论的建议后,这是我能想到的最有效的 MapList<TSource, TDestination>
实施:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
public static List<TDestination> MapList<TSource, TDestination>(
this IMapper<TSource, TDestination> translator,
List<TSource> source)
where TDestination : new()
{
var destinationCollection = new List<TDestination>(source.Count);
foreach (var sourceItem in source)
{
TDestination dest = Factory<TDestination>.Instance();
translator.Map(sourceItem, dest);
destinationCollection.Add(dest);
}
return destinationCollection;
}
static class Factory<T>
{
// Cached "return new T()" delegate.
internal static readonly Func<T> Instance = CreateFactory();
private static Func<T> CreateFactory()
{
NewExpression newExpr = Expression.New(typeof(T));
return Expression
.Lambda<Func<T>>(newExpr)
.Compile();
}
}
请注意,我设法利用 Jon Skeet 的建议 not 来使用 new TDestination()
而无需调用者提供 Func<TDestination>
委托,从而保留你的 API.
当然,编译工厂委托的成本是不可忽略的,但在常见的映射场景中,我认为这是值得的。
如何在保持 public 界面的同时进一步提高下面代码的性能:
public interface IMapper<in TSource, in TDestination>
{
void Map(TSource source, TDestination destination);
}
public static TDestination Map<TSource, TDestination>(
this IMapper<TSource, TDestination> translator,
TSource source)
where TDestination : new()
{
var destination = new TDestination();
translator.Map(source, destination);
return destination;
}
public static List<TDestination> MapList<TSource, TDestination>(
this IMapper<TSource, TDestination> translator,
List<TSource> source)
where TDestination : new()
{
var destinationCollection = new List<TDestination>(source.Count);
foreach (var sourceItem in source)
{
var destinationItem = translator.Map(sourceItem);
destinationCollection.Add(destinationItem);
}
return destinationCollection;
}
用法示例
public class MapFrom { public string Property { get; set; } }
public class MapTo { public string Property { get; set; } }
public class Mapper : IMapper<MapFrom, MapTo>
{
public void Map(MapFrom source, MapTo destination)
{
destination.Property = source.Property;
}
}
var mapper = new Mapper();
var mapTo = mapper.Map(new MapFrom() { Property = "Foo" });
var mapToList = mapper.MapList(
new List<MapFrom>()
{
new MapFrom() { Property = "Foo" }
});
当前基准
当我 运行 针对原始手动转换的基准时,这些是我得到的数字:
| Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Scaled | ScaledSD | Gen 0 | Allocated |
|------------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|-------:|---------:|-------:|----------:|
| Baseline | Clr | Clr | 1.969 us | 0.0354 us | 0.0332 us | 1.927 us | 2.027 us | 1.00 | 0.00 | 2.0523 | 6.31 KB |
| Mapper | Clr | Clr | 9.016 us | 0.1753 us | 0.2019 us | 8.545 us | 9.419 us | 4.58 | 0.12 | 2.0447 | 6.31 KB |
| Baseline | Core | Core | 1.820 us | 0.0346 us | 0.0355 us | 1.777 us | 1.902 us | 1.00 | 0.00 | 2.0542 | 6.31 KB |
| Mapper | Core | Core | 9.043 us | 0.1725 us | 0.1613 us | 8.764 us | 9.294 us | 4.97 | 0.13 | 2.0447 | 6.31 KB |
这里是基线代码:
var mapTo = new MapTo() { Property = mapFrom.Property };
var mapToCollection = new List<MapTo>(this.mapFrom.Count);
foreach (var item in this.mapFrom)
{
destination.Add(new MapTo() { Property = item.Property });
}
基准代码
我在 Dotnet-Boxed/Framework GitHub 存储库中有一个包含映射器和 Benchmark.NET 项目的完整工作项目。
在实施评论中讨论的建议后,这是我能想到的最有效的 MapList<TSource, TDestination>
实施:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
public static List<TDestination> MapList<TSource, TDestination>(
this IMapper<TSource, TDestination> translator,
List<TSource> source)
where TDestination : new()
{
var destinationCollection = new List<TDestination>(source.Count);
foreach (var sourceItem in source)
{
TDestination dest = Factory<TDestination>.Instance();
translator.Map(sourceItem, dest);
destinationCollection.Add(dest);
}
return destinationCollection;
}
static class Factory<T>
{
// Cached "return new T()" delegate.
internal static readonly Func<T> Instance = CreateFactory();
private static Func<T> CreateFactory()
{
NewExpression newExpr = Expression.New(typeof(T));
return Expression
.Lambda<Func<T>>(newExpr)
.Compile();
}
}
请注意,我设法利用 Jon Skeet 的建议 not 来使用 new TDestination()
而无需调用者提供 Func<TDestination>
委托,从而保留你的 API.
当然,编译工厂委托的成本是不可忽略的,但在常见的映射场景中,我认为这是值得的。