AutoMapper vs. Manual Mapping in .NET
When working with object-to-object mapping in .NET, developers often debate between using AutoMapper a powerful library for automatic mapping and writing manual mappings. To see how these approaches compare in terms of performance, I set up a BenchmarkDotNet test for the four data structures described below.
The Setup
I created:
- Source and Destination classes.
- Source and Destination records.
- Source and Destination record structs.
All share the same set of properties:
- Basic fields like
Id
,Name
,Description
,CreatedAt
- Ten extra fields (
Field1
,Field2
, etc.) of various types (int
,string
,double
,decimal
,bool
,DateTime
,Guid
,long
,float
,char
).
Mapping Approaches
AutoMapper
We configured mappings for each source/destination pair:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceClass, DestinationClass>();
cfg.CreateMap<SourceRecord, DestinationRecord>();
cfg.CreateMap<SourceRecordStruct, DestinationRecordStruct>();
});
The library handles property assignments under the hood, making code concise but introducing a performance overhead.
Manual Mapping
We explicitly assign properties in code. For example, mapping from a SourceClass
to a DestinationClass,DestinationRecord and DestinationRecordStruct
:
public class SourceClass
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime CreatedAt { get; set; }
public int Field1 { get; set; }
public string Field2 { get; set; }
public double Field3 { get; set; }
public decimal Field4 { get; set; }
public bool Field5 { get; set; }
public DateTime Field6 { get; set; }
public Guid Field7 { get; set; }
public long Field8 { get; set; }
public float Field9 { get; set; }
public char Field10 { get; set; }
}
public class DestinationClass
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime CreatedAt { get; set; }
public int Field1 { get; set; }
public string Field2 { get; set; }
public double Field3 { get; set; }
public decimal Field4 { get; set; }
public bool Field5 { get; set; }
public DateTime Field6 { get; set; }
public Guid Field7 { get; set; }
public long Field8 { get; set; }
public float Field9 { get; set; }
public char Field10 { get; set; }
}
public record DestinationRecord(
int Id,
string Name,
string Description,
DateTime CreatedAt,
int Field1,
string Field2,
double Field3,
decimal Field4,
bool Field5,
DateTime Field6,
Guid Field7,
long Field8,
float Field9,
char Field10
);
public readonly record struct DestinationRecordStruct(
int Id,
string Name,
string Description,
DateTime CreatedAt,
int Field1,
string Field2,
double Field3,
decimal Field4,
bool Field5,
DateTime Field6,
Guid Field7,
long Field8,
float Field9,
char Field10
)
Benchmark Configuration
- BenchmarkDotNet: We used
[MemoryDiagnoser]
to measure allocations and[SimpleJob(RuntimeMoniker.Net90)]
to run on .NET 9.0. - Each mapping method is a separate benchmark:
AutoMapperMapping()
Vs.ManualMapping()
AutoMapperMappingRecord()
Vs.ManualMappingRecord()
AutoMapperMappingRecordStruct()
Vs.ManualMappingRecordStruct()
The Results
Here is the BenchmarkDotNet output. Notice both the Meantime (in nanoseconds) and the Allocated memory (in bytes):
Observations
Manual Mapping is notably faster across the board — by a factor of ~3 to 10.
Record Struct sees the largest performance gap:
- AutoMapper ~96 ns
- Manual mapping ~8.5 ns
Memory Allocations:
- AutoMapper consistently allocates ~120 bytes.
- Manual mapping with records and record structs shows no additional heap allocation (
-
in the table).
Analyzing the Trade-Offs
Convenience vs. Performance
AutoMapper reduces boilerplate and can make code more readable. However, it introduces overhead in both CPU cycles and memory allocations.
Context Matters
- If you rarely map large numbers of objects in performance-critical paths, the overhead is likely negligible.
- If your application needs to map thousands or millions of objects per second, the manual approach (especially with record structs) can yield significant performance gains.
Maintainability
Manual mapping can become cumbersome if you have many properties or types. Each new property requires a code change. AutoMapper automatically maps properties with matching names, reducing maintenance effort.
Conclusion
Which should you choose? It depends on your specific use case:
- Use AutoMapper if you value simplicity, maintainability, and developer productivity and are not hitting performance bottlenecks in mapping.
- Use Manual Mapping if every nanosecond counts, in real-time or high-throughput scenarios and you’re willing to invest in writing and maintaining explicit mapping code.
By running these benchmarks, we see a clear performance advantage for manual mapping, especially with record structs. However, this may not be the deciding factor in every application. Evaluate your needs, measure real-world performance, and pick the approach that best balances speed and maintainability.