TinyMapper: Yet Another Object to Object Mapper for .NET - CodeProject

:

Introduction

TinyMapper is an object to object mapper for .NET. The main advantage is performance. TinyMapper allows easily map object to object, i.e. properties or fields from one object to another, for instance.

private static void MapPerson()
{
    TinyMapper.Bind<Person, PersonDto>();

    var person = new Person
    {
        Id = Guid.NewGuid(),
        FirstName = "John",
        LastName = "Doe",
        Email = "support@tinymapper.net",
        Address = "Wall Street",
        CreateTime = DateTime.Now,
        Nickname = "Object Mapper",
        Phone = "Call Me Maybe "
    };

    var personDto = TinyMapper.Map<PersonDto>(person);
}

Performance

Yes, this chapter has to be on the end. I believe most of you know what object mappers are, so let's talk about performance briefly.

We'll use the same Person class for the testing purpose.

public sealed class Person
{
    public string Address { get; set; }
    public string Email { get; set; }
    public string FirstName { get; set; }
    public Guid Id { get; set; }
    public string LastName { get; set; }
    public string Nickname { get; set; }
    public DateTime CreateTime { get; set; }
    public string Phone { get; set; }
}

PersonDto class is the same as the Person. We'll compare:

During the test, we create a Person class instance and map all properties on the PersonDto class.

private static void MeasureHandwritten()
{
    Person person = CreatePerson();

    Stopwatch stopwatch = Stopwatch.StartNew();

    for (int i = 0; i < Iterations; i++)
    {
        PersonDto personDto = MapHandwritten(person);
    }
    stopwatch.Stop();
    Console.WriteLine("Handwritten: {0}ms", stopwatch.Elapsed.TotalMilliseconds);
}

private static void MeasureTinyMapper()
{
    Person person = CreatePerson();
    TinyMapper.Bind<Person, PersonDto>();

    Stopwatch stopwatch = Stopwatch.StartNew();

    for (int i = 0; i < Iterations; i++)
    {
        var personDto = TinyMapper.Map<PersonDto>(person);
    }

    stopwatch.Stop();
    Console.WriteLine("TinyMapper: {0}ms", stopwatch.Elapsed.TotalMilliseconds);
}

private static void MeasureAutoMapper()
{
    Person person = CreatePerson();
    Mapper.CreateMap<Person, PersonDto>();

    Stopwatch stopwatch = Stopwatch.StartNew();

    for (int i = 0; i < Iterations; i++)
    {
        var personDto = Mapper.Map<PersonDto>(person);
    }
    stopwatch.Stop();
    Console.WriteLine("AutoMapper: {0}ms", stopwatch.Elapsed.TotalMilliseconds);
}

where CreatePerson

private static Person CreatePerson()
{
    return new Person
    {
        Id = Guid.NewGuid(),
        FirstName = "John",
        LastName = "Doe",
        Email = "support@tinymapper.net",
        Address = "Wall Street",
        CreateTime = DateTime.Now,
        Nickname = "Object Mapper",
        Phone = "Call Me Maybe "
    };
}

and MapHandwritten

private static PersonDto MapHandwritten(Person person)
{
    var result = new PersonDto
    {
        Id = person.Id,
        FirstName = person.FirstName,
        LastName = person.LastName,
        Email = person.Email,
        Address = person.Address,
        CreateTime = person.CreateTime,
        Nickname = person.Nickname,
        Phone = person.Phone
    };
    return result;
}

we repeat measurement 1 000 000 times.

 

Getting Started

TinyMapper is available thru nugen, so you can install as all NuGet package. for example through Package Manager Console.

Okay, we're ready to start. First of all, you've to Bind source type to target type, like this.

TinyMapper.Bind<Person, PersonDto>();

Now TinyMapper knows how to map Person object to PersonDto object. Finally, call

var personDto = TinyMapper.Map<PersonDto>(person);

and you'll get mapped object. Here's the full version.

So you have to do two steps:

  • Bind source type to the target type
  • Map source object to the target type

Quote:

Note: if you're multithreading an application, register all bindings on application start. While the application is running, you'll call only Map method. So, you'll get speed and thread safe.

TinyMapper can do the first step for you, i.e., you can just call Map and the result will be the same.

Quote:

Note: Be careful to call Map without Bind, it's not thread safe.

More Complex Example

Let's take a look at a more complex sample. For example, we have the following classes.

public sealed class PersonComplex
{
    public Address Address { get; set; }
    public DateTime CreateTime { get; set; }
    public IReadOnlyList<string> Emails { get; set; }
    public string FirstName { get; set; }
    public Guid Id { get; set; }
    public string LastName { get; set; }
    public string Nickname { get; set; }
}

public sealed class Address
{
    public string Phone { get; set; }
    public string Street { get; set; }
    public string ZipCode { get; set; }
}

and we don't want to map CreateTime and Nickname properties.

As you can see, we've just marked CreateTime and Nickname as ignored. Bind LastName from the source Surname to the target class and bind IList of emails to List

TinyMapper allows:

  • Ignore fields
  • Map one field to a different field
  • Map interface to exact type

Custom Mapping

For instance, we have the following classes:

public sealed class PersonCustomSource
{
    public IList<string> Emails { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public sealed class PersonCustomTarget
{
    public IList<string> Emails { get; set; }
    public string FullName { get; set; }
}

We want to map:

  • FirsName and LastName to FullName with the following convention "FirstName LastName"

TinyMapper supports TypeConverter, i.e., you can create your own TypeConverter for any type.

public sealed class PersonTypeConverter : TypeConverter
{
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        return destinationType == typeof(PersonCustomTarget);
    }

    public override object ConvertTo
    (ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        var concreteValue = (PersonCustomSource)value;
        var result = new PersonCustomTarget
        {
            FullName = string.Format("{0} {1}", concreteValue.FirstName, concreteValue.LastName),
            Emails = new List<string>(concreteValue.Emails)
        };
        return result;
    }
}

The next step is to register the type converter. TypeConverter supports registration through attribute...

[TypeConverter(typeof(PersonTypeConverter))]
public sealed class PersonCustomSource
{
    public IList<string> Emails { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

...or code

TypeDescriptor.AddAttributes(typeof(PersonCustomSource), 
	new TypeConverterAttribute(typeof(PersonTypeConverter)));

Here's an additional sample of how to implement a type converter.

Under the Hood

It's a long story. In the next article, I'll describe in detail about how TinyMapper works. Internally, TinyMapper generates mapping code through ILGenerator. The mapping code looks almost the same as handwritten code. For instance, the following code was generated for Person to PersonDto mapping.

I hope you enjoyed it. Thanks for reading the article.