Building a Blazing Fast Object-to-Object Mapper in C# with .NET Core 3.1
Time to read: 8 minutes
Object-to-object mapping is used mostly for copying properties of one object to another, typically for transforming an object from one type to another. There are many use cases. For example, object transformation can help prevent overposting attacks from succeeding by intermediating between APIs and user interfaces exposed to the world and private data models on back-end servers.
You can build your own minimalistic object-to-object mapper with C# and .NET Core without writing a lot of code. This post will show you various approaches, one of which is used in production for an online action game with a massive load.
There are also professional mappers available that come with many useful features the minimalistic code doesn’t provide. This post will show you how to implement AutoMapper, one of the most popular object-to-object mapping libraries.
The mapping techniques demonstrated here were originally developed in 2011. At the time, C# Reflection was even more costly from a performance standpoint than it is today. There was, and still is, a strong motivation to find a faster way of mapping. The approaches demonstrated in this post chart the course of a short technical journey from simple Reflection-based code to dynamic code generation in the search for better performance. As you travel the path yourself you’ll have the opportunity to see the difference in performance between the various approaches and how performance has changed over time.
If you would like to see a full integration of Twilio APIs in a .NET Core application then checkout this free 5-part video series. It's separate from this blog post tutorial but will give you a full run down of many APIs at once.
Understanding the case study project
In the following coding exercise you’ll create four different ways of implementing an object mapper. You’ll also implement the popular AutoMapper library, which provides a suite of highly-optimized object mapping tools. To compare the performance of each approach you’ll create a test that executes each implementation a million times and displays the elapsed time.
Prerequisites
To follow along with the coding exercises presented in this post you’ll need the following tools:
.NET Core SDK (The SDK download includes the .NET CLI and the .NET Runtime.)
Visual Studio 2019, Visual Studio Code, or an IDE or text editor of your choice
Git (If you want to clone the project from GitHub.)
.NET Core is a cross-platform technology: the code in this post will run on Windows, macOS, and Linux.
In addition to the technical resources, you should have a working knowledge of:
- C#, including abstract classes, generic types, LINQ, and Reflection
- .NET Core, including the LINQ and Reflection APIs
- Creating and running .NET Core Console applications with VS 2019, VS Code, or the .NET CLI.
It will also be helpful to have some exposure to the Roslyn code analyzers.
There is a companion repository available on GitHub.
Initializing the case study project
Create a new .NET Core Console application called ObjectToObjectMapper.
Mapping the properties of two objects
The first step in object mapping is to find the matching properties of two objects. You can use the PropertyInfo class of the System.Reflection API, but you can also take a step further and be better prepared by creating a class, PropertyMap
, to hold information about the matching properties.
Add a new C# class file to your project root folder named PropertyMap.cs. Replace the existing contents of the class file with the following code:
This class will match one property from the source object and one from the target object. To find properties to be mapped you need also some code to generate a full property map.
Add another C# class file to your project’s root folder named ObjectExtensions.cs and replace the contents with the following code which provides the GetMatchingProps()
method you need to create a property map:
This extension method is smart: it matches only those properties that can be read in the source object and that are writable in the target object. Matched properties from both objects are wrapped in the PropertyMap
class and all matching properties are returned as list of PropertyMap
objects.
The ObjectExtensions
class is for your reference. You won’t use it in the mapping implementations you’re going to code next, but some of its code will reappear. It’s not included in the companion repository.
Comparing different approaches to object mapping
You will create four versions of an object-to-object mapper:
- Unoptimized
- Optimized
- Dynamic code
- Lightweight Code Generator (LGC)
Each version is more complex than the previous one.
Creating a mapping base class
To avoid repeating code you’ll need a base class for mappers.
Create a new C# class file in the root folder of your project named ObjectCopyBase.cs and replace the contents with the following code:
The GetMatchingProperties
base class is similar to the GetMatchingProps
method you implemented in the ObjectExtensions
class. Notice that the base class also has a GetMapKey()
method that generates the type map key for the mappings cache.
Implementing an unoptimized mapper
The first mapper you will create is unoptimized. It’s dumb and hurts performance badly compared to all the other versions you will write. The only purpose of the unoptimized mapper is to show you the price of using Reflection.
Add a new C# class file to your project root folder called MapperUnoptimized.cs and replace the contents with the following code:
Experienced developers will probably have a heart attack if they see this code is about to go live. A test of the speed of this mapper produced 0.0042 milliseconds as an average for a million runs. As you’ll see, that’s substantially slower than any of the following approaches.
Implementing an optimized mapper
You can’t go live with an unoptimized mapper; you must make it work better. What you can do is to keep the property map cached so it’s generated only once.
Add a new C# class file to your project’s root folder named MapperOptimized.cs and replace the contents with the following code:
A test run for this method produced a 0.0023 milliseconds average. It’s twice as fast as the unoptimized version.
Implementing mapping using dynamic code
Both previous implementations make heavy use of Reflection when copying object properties. Since Reflection is slow, it is probably the only place in the mapping code that you can optimize significantly.
One way you can avoid Reflection is to generate code dynamically. Generated C# code copies property values one-by-one from one object to another. Perhaps it’s not a perfect approach, but there’s a chance to gain something in performance.
To use dynamic code you’ll need to install the Microsoft.CodeAnalysis.CSharp NuGet package. This is the .NET CLI command for the current release version as of the date of this post:
Add a new class file to your project root folder named MapperDynamicCode.cs and replace the contents with the following code:
Compared to the previous versions it’s faster. The test run result was 0.0014 milliseconds.
Implementing object mapping using Lightweight Code Generation (LGC)
Your next experiment is to use Lightweight Code Generation. It’s an API to generate code by emitting Intermedia Language (IL) instructions directly. There’s no C#, only IL instructions, and so no C# compiler is involved.
Add a new C# class file to your project’s root folder named MapperLcg.cs and replace the contents with the following code:
With this mapper a test run gave an average result of 0.0013 milliseconds for 1 million iterations.
Comparing the performance of object mapping methodologies over time
Over the years the C# language and its compiler have improved. Also, the .NET Framework is not the same as before. Now that you’ve built object mappers with the latest version of .NET Core, it’s time to take a look back at the original experiment and compare results for the various approaches you’ve implemented.
Then, as now, the mapping techniques you’ve implemented are benchmarked against AutoMapper, a component with a long history during which it has acquired tons of optimizations.
Implementation |
Old (ms) |
New (ms) |
Difference (x) |
MapperUnoptimized |
0.0403 |
0.0042 |
9.6 |
MapperOptimized |
0.0240 |
0.0023 |
10.4 |
MapperDynamicCode |
0.0058 |
0.0014 |
4.1 |
MapperLcg |
0.0019 |
0.0013 |
1.5 |
AutoMapper |
0.0118 |
0.0002 |
59.0 |
These results clearly show that the current version of .NET Core is more performant. There is substantially better performance for the first two mappers, which don’t use any advanced approaches. Also notice the last line. The authors of AutoMapper have done a great job optimizing their mapper and now it seriously beats all the minimalistic approaches.
Testing object mapper methodologies
You can write a simple test of the object mappers you’ve created by using the Stopwatch class of the System.Diagnostic namespace. The Stopwatch
class measures the actual time it takes code to run. To get a reliable value, every mapper is invoked one million times and the average time is reported as the result.
You can also compare the performance of the various approaches to object mapping that you’ve implemented in this project to the results from the AutoMapper library. To do that, you’ll need to add the AutoMapper NuGet package to your project. The following is the appropriate .NET CLI command:
When the package is successfully installed, replace the contents of Program.cs with the following code:
Run the program. After a brief wait (depending on your hardware) you should start to see results like the following appearing in the console window opened by the program:
If your results are typical, you should see that AutoMapper is approximately an order of magnitude faster than the other approaches. While it might be appropriate to implement your own mapper in some circumstances, you’ll need to make many optimizations to equal the performance of this popular library.
Summary
Congratulations, you just walked the path from a simple object-to-object mapper to more performant dynamic approaches! You’ve also seen how the relative speed of these techniques has changed after nine long years. Many things have changed: there are newer and better versions of operating systems, the .NET Framework has evolved into .NET Core, compilers have improved, and hardware is faster. The numbers from past tests are very different from current results.
The journey you just went through also taught you some other important things: Reflection comes with a price, but dynamic code performs very well today. You also saw how to use lightweight code generation. These approaches have stood the test of time, and they have often been the starting points of solutions for other problems.
Additional resources
There are a number of documentation references, articles, and posts that may be useful to you as you dig deeper into the topics mentioned in this post:
Introduction to the Roslyn Scripting API – This post provides some background on the technologies providing the foundation of Lightweight Code Generation.
Code Generation on .NET – A brief blog post providing an overview of the different kinds of code generation in .NET and their advantages and disadvantages.
Reflection in .NET – Canonical documentation for the System.Reflection namespace.
Gunnar Peipman is ASP.NET, Azure, and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he has been a Microsoft MVP specializing in ASP.NET. You can reach him through his website, gunnarpeipman.com.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.