Originally published on Medium · March 15, 2023 — examples target the tooling of that time.
Every C# codebase I've touched ends up needing to shuffle data between layers — entities, DTOs, view models — and sooner or later someone asks whether the mapping library is quietly eating performance. So I put the usual suspects on the bench together: a hand-rolled inline mapper, plain reflection, and five popular libraries.
The results vary more than you'd expect once reflection gets involved, and the gap matters if you're mapping objects in a hot path rather than a one-off admin screen.
Below is what I found when benchmarking speed and memory usage across these approaches.
Inline Object-to-Object Mapping
Inline O2O mapper is a lightweight and simple way to map objects in C#. It requires no additional libraries or configurations. This mapper is ideal for small object graphs and simple mappings. However, for larger object graphs, it may not be as performant as other mappers that use more advanced mapping algorithms.
public class ObjectToObjectTests
{
[Benchmark]
public void Inline()
{
var payment = new Payment()
{
Amount = 120,
Created = DateTimeOffset.Now,
Id = Guid.NewGuid(),
Status = "Created"
};
var paymentEntity = new PaymentEntity()
{
Amount = payment.Amount,
Created = payment.Created,
Id = payment.Id,
Status = payment.Status
};
}
}
2. Plain Reflection
Reflection-based mappers are widely used in C# and are a common approach for O2O mapping. Reflection-based mappers work by reflecting over the properties of the source and target objects and then mapping them accordingly. This method is easy to use and requires no additional libraries, but it can be slow and memory-intensive, especially for large object graphs.
public class ObjectToObjectTests
{
[Benchmark]
public void Reflection()
{
var payment = new Payment()
{
Amount = 120,
Created = DateTimeOffset.Now,
Id = Guid.NewGuid(),
Status = "Created"
};
var paymentEntity = ReflectionMapper.MapObjects<Payment, PaymentEntity>(payment);
}
}
public static class ReflectionMapper
{
public static TTo MapObjects<TFrom, TTo>(TFrom sourceObject) where TTo : new()
{
TTo targetObject = new TTo();
Type sourceType = sourceObject.GetType();
PropertyInfo[] sourceProperties = sourceType.GetProperties();
PropertyInfo[] targetProperties = typeof(TTo).GetProperties();
foreach (PropertyInfo sourceProperty in sourceProperties)
{
PropertyInfo targetProperty = targetProperties.FirstOrDefault(p => p.Name == sourceProperty.Name && p.PropertyType == sourceProperty.PropertyType);
if (targetProperty != null && targetProperty.CanWrite)
{
object value = sourceProperty.GetValue(sourceObject);
targetProperty.SetValue(targetObject, value);
}
}
return targetObject;
}
}
3. AutoMapper
AutoMapper is a popular open-source O2O mapper for .NET that uses reflection-based mapping. AutoMapper is highly configurable and can be used with LINQ to project objects, making it a powerful tool for data manipulation. However, the configuration required to use AutoMapper can be complex, and the reflection-based mapping may not be as performant as other mappers that use more advanced algorithms.
public class ObjectToObjectTests
{
private readonly AutoMapper.Mapper _autoMapper;
public ObjectToObjectTests()
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Payment, PaymentEntity>());
_autoMapper = new AutoMapper.Mapper(config);
}
[Benchmark]
public void AutoMapper()
{
var payment = new Payment()
{
Amount = 120,
Created = DateTimeOffset.Now,
Id = Guid.NewGuid(),
Status = "Created"
};
var paymentEntity = _autoMapper.Map<PaymentEntity>(payment);
}
}
4. TinyMapper
TinyMapper is a lightweight, open-source O2O mapper for .NET that uses code generation to create mappings. This method results in faster mapping times than traditional reflection-based mappers, making it a great option for larger object graphs. TinyMapper is easy to use and requires minimal configuration. However, it does not support advanced features like mapping of nested objects.
public class ObjectToObjectTests
{
public ObjectToObjectTests()
{
TinyMapper.Bind<Payment, PaymentEntity>();
}
[Benchmark]
public void TinyMapperTest()
{
var payment = new Payment()
{
Amount = 120,
Created = DateTimeOffset.Now,
Id = Guid.NewGuid(),
Status = "Created"
};
var paymentEntity = TinyMapper.Map<PaymentEntity>(payment);
}
}
5. Mapster
Mapster is a lightweight, open-source O2O mapper for .NET that uses both reflection-based and code-generation-based mapping. Mapster is designed to be easy to use and requires minimal configuration. It supports mapping of nested objects and can handle complex object graphs. However, the use of both reflection and code-generation may impact performance in some cases.
public class ObjectToObjectTests
{
[Benchmark]
public void Mapster()
{
var payment = new Payment()
{
Amount = 120,
Created = DateTimeOffset.Now,
Id = Guid.NewGuid(),
Status = "Created"
};
var paymentEntity = payment.Adapt<PaymentEntity>();
}
}
6. ValueInjecter
ValueInjecter is a lightweight, open-source O2O mapper for .NET that uses reflection-based mapping. ValueInjecter is designed to be simple and easy to use, with minimal configuration required. It supports mapping of nested objects and can handle complex object graphs. However, the reflection-based mapping may not be as performant as other mappers that use more advanced algorithms.
public class ObjectToObjectTests
{
[Benchmark]
public void ValueInjecter()
{
var payment = new Payment()
{
Amount = 120,
Created = DateTimeOffset.Now,
Id = Guid.NewGuid(),
Status = "Created"
};
var paymentEntity = Omu.ValueInjecter.Mapper.Map<PaymentEntity>(payment);
}
}
7. FastMapper
FastMapper is a high-performance, open-source O2O mapper for .NET that uses code generation to create mappings. This method results in faster mapping times than traditional reflection-based mappers. FastMapper is designed to be easy to use, with a fluent API and minimal configuration required. It supports custom type conversions and can handle complex object graphs.
public class ObjectToObjectTests
{
[Benchmark]
public void FastMapper()
{
var payment = new Payment()
{
Amount = 120,
Created = DateTimeOffset.Now,
Id = Guid.NewGuid(),
Status = "Created"
};
var paymentEntity = TypeAdapter.Adapt<Payment, PaymentEntity>(payment);
}
}
Benchmark Results
BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1413/22H2/2022Update/SunValley2)
11th Gen Intel Core i7-1165G7 2.80GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=7.0.100
[Host] : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2 [AttachedDebugger]
DefaultJob : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2
| Method | Mean | Error | StdDev | Median | Gen0 | Allocated |
|--------------- |-----------:|---------:|----------:|-----------:|-------:|----------:|
| Inline | 317.8 ns | 6.20 ns | 7.38 ns | 315.4 ns | 0.0229 | 144 B |
| FastMapper | 318.9 ns | 12.85 ns | 35.40 ns | 311.9 ns | 0.0229 | 144 B |
| Mapster | 328.3 ns | 6.52 ns | 12.56 ns | 323.9 ns | 0.0229 | 144 B |
| TinyMapper | 333.2 ns | 6.52 ns | 8.92 ns | 335.6 ns | 0.0229 | 144 B |
| AutoMapper | 406.5 ns | 8.04 ns | 12.99 ns | 405.4 ns | 0.0229 | 144 B |
| ValueInjecter | 1,263.2 ns | 36.90 ns | 108.21 ns | 1,215.5 ns | 0.0515 | 328 B |
| Reflection | 1,299.5 ns | 24.97 ns | 46.28 ns | 1,300.0 ns | 0.1316 | 832 B |
Which Mapper Should You Use?
When choosing an object to object mapper for C#, developers have several options to consider. The performance analysis shows that for small object graphs, the Inline O2O mapper is the fastest, followed closely by FastMapper. For larger and more complex object graphs, code-generation-based mappers like TinyMapper and FastMapper may be more performant. Reflection-based mappers like AutoMapper and ValueInjecter are easy to configure, but may not be the most performant option for larger object graphs. Ultimately, the best choice depends on the size and complexity of the object graph and specific performance requirements.
Hope this helps!