Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to automatically map the values between instances of two different classes if both have same properties?

Tags:

c#

I have two classes with exactly same members (properties and fields) with same datatypes. I want to map the members from one to other in automated way. I know there are more practical means development-wise to handle. One simple solution is to manually map each member from one instance to other. But, I want to automate this as some general solution.

Assuming you have the following code:

public MyObject1 AssignTest (MyObject1 obj1, MyObject2 obj2)
{
    //Code here for auto map
}

Where MyObject1 and MyObject2 have the exact same properties with same datatype. I do not want to go through and assign the values individually (i.e. MyObject1.Property1 = MyObject2.Property1 etc.). Is it possible to assign all the values that have been specified in MyObject1 to MyObject2 automatically?

like image 788
user3010406 Avatar asked Dec 05 '13 20:12

user3010406


2 Answers

One possibility to make this (for example for the purpose of creating your own automapper or understand how it basically works) would be to use (as already suggested) Reflection. The code can look like this:

// TODO: error handling
// Test classes
public class A
{
    public string Name { get; set; }
    public int Count;
}

public class B
{
    public string Name { get; set; }
    public int Count;
}
// copy routine
public B CopyAToB(A a)
{
    B b = new B();
    // copy fields
    var typeOfA = a.GetType();
    var typeOfB = b.GetType();
    foreach (var fieldOfA in typeOfA.GetFields())
    {
        var fieldOfB = typeOfB.GetField(fieldOfA.Name);
        fieldOfB.SetValue(b, fieldOfA.GetValue(a));
    }
    // copy properties
    foreach (var propertyOfA in typeOfA.GetProperties())
    {
        var propertyOfB = typeOfB.GetProperty(propertyOfA.Name);
        propertyOfB.SetValue(b, propertyOfA.GetValue(a));
    }

    return b;
}

The function can be used like this:

var a = new A
{
    Name = "a",
    Count = 1
};

var b = CopyAToB(a);
Console.Out.WriteLine(string.Format("{0} - {1}", b.Name, b.Count));

The output is:

a - 1

Please note, that the usage of reflection comes with a price - it costs performance. Using reflection you can access both private and public object members. This is for example used from Visual Studio to create test accessor objects in order to access all test object members.

Please have a look at the existing automappers (see the other answers for links) and use them instead of reinventing the wheel by yourself - the existing libraries are optimized for speed, thoroughly tested and are very comfortable to use. This way you will minimize errors in your code.

like image 94
keenthinker Avatar answered Oct 23 '22 14:10

keenthinker


Extending from accepted answer from @pasty, I have created generic method for this purpose.

public static TDest MapSourceToDest<TSource, TDest>(TSource source)
                                    where TSource : class//We are not creating an instance of source, no need to restrict parameterless constructor
                                    where TDest : class, new()//We are creating an instance of destination, parameterless constructor is needed
{
    if(source == null)
        return null;

    TDest destination = new TDest();

    var typeOfSource = source.GetType();
    var typeOfDestination = destination.GetType();

    foreach(var fieldOfSource in typeOfSource.GetFields())
    {
        var fieldOfDestination = typeOfDestination.GetField(fieldOfSource.Name);
        if(fieldOfDestination != null)
        {
            try
            { fieldOfDestination.SetValue(destination, fieldOfSource.GetValue(source)); }
            catch(ArgumentException) { }//If datatype is mismatch, skip the mapping
        }
    }

    foreach(var propertyOfSource in typeOfSource.GetProperties())
    {
        var propertyOfDestination = typeOfDestination.GetProperty(propertyOfSource.Name);
        if(propertyOfDestination != null)
        {
            try
            { propertyOfDestination.SetValue(destination, propertyOfSource.GetValue(source)); }
            catch(ArgumentException) { }//If datatype is mismatch, skip the mapping
        }
    }

    return destination;
}

One may need to alter the filters on generic types; but everything else will work cross any types. Null check is added for fieldOfDestination and propertyOfDestination just in case a member is missing; this adds up little more flexibility.

like image 34
Amit Joshi Avatar answered Oct 23 '22 14:10

Amit Joshi