How to copy value from class X to class Y with the same property name in c#?
Asked Answered
D

5

16

Suppose I have two classes:

public class Student
{
    public int Id {get; set;}
    public string Name {get; set;}
    public IList<Course> Courses{ get; set;}
}

public class StudentDTO
{
    public int Id {get; set;}
    public string Name {get; set;}
    public IList<CourseDTO> Courses{ get; set;}
}

I would like to copy value from Student class to StudentDTO class:

var student = new Student();
StudentDTO studentDTO = student;

How can I do that by reflection or other solution?

Dioecious answered 10/2, 2009 at 8:40 Comment(1)
Take a look at Automapper. That tool was designed to handle this exact scenario.Reachmedown
S
17

The lists make it tricky... my earlier reply (below) only applies to like-for-like properties (not the lists). I suspect you might just have to write and maintain code:

    Student foo = new Student {
        Id = 1,
        Name = "a",
        Courses = {
            new Course { Key = 2},
            new Course { Key = 3},
        }
    };
    StudentDTO dto = new StudentDTO {
        Id = foo.Id,
        Name = foo.Name,
    };
    foreach (var course in foo.Courses) {
        dto.Courses.Add(new CourseDTO {
            Key = course.Key
        });
    }

edit; only applies to shallow copies - not lists

Reflection is an option, but slow. In 3.5 you can build this into a compiled bit of code with Expression. Jon Skeet has a pre-rolled sample of this in MiscUtil - just use as:

Student source = ...
StudentDTO item = PropertyCopy<StudentDTO>.CopyFrom(student);

Because this uses a compiled Expression it will vastly out-perform reflection.

If you don't have 3.5, then use reflection or ComponentModel. If you use ComponentModel, you can at least use HyperDescriptor to get it nearly as quick as Expression

Student source = ...
StudentDTO item = new StudentDTO();
PropertyDescriptorCollection
     sourceProps = TypeDescriptor.GetProperties(student),
     destProps = TypeDescriptor.GetProperties(item),
foreach(PropertyDescriptor prop in sourceProps) {
    PropertyDescriptor destProp = destProps[prop.Name];
    if(destProp != null) destProp.SetValue(item, prop.GetValue(student));
}
Splenectomy answered 10/2, 2009 at 8:42 Comment(8)
very quick... I was just typing and got a bad message that there is new answer and its ... So I abort :(Neckcloth
Would you have any problems with the CourseDTO list? because CourseDTO can be something different than CourseBenedikta
Marc, awesome link to MiscUtil. That code is so elegant. I hope you don't mind me posting it here because people really should see it. I'm modding your answer up man! That link was a great find.Eringo
Oh, MiscUtil doesn't help for my CourseDTO list. Other any solution? Because my class has a lot of properties and child list.Dioecious
It would perhaps be possible to get the expression to invoke the same expression approach recursively for the children... the problem is that there would need to be some "fluent" wrappers along the way due to limitations inside Expression in 3.5 (fixed in 4.0).Splenectomy
Unfortunately, I don't have any time to investigate re-writing it this morning; suggest you use the helper approach for the children. and copy the parent manually?Splenectomy
@Marc: Shouldn't be StudentDTO item = PropertyCopy<StudentDTO>.CopyFrom(source); ?Compose
@Compose probablySplenectomy
E
10

Ok I just looked up the MiscUtil that Marc posted about and its just awesome. I hope mark doesn't mind me adding the code here.

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class Program
    {
        public class Student
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public IList<int> Courses { get; set; }
            public static implicit operator Student(StudentDTO studentDTO)
            {
                return PropertyCopy<Student>.CopyFrom(studentDTO);
            }
        }

        public class StudentDTO
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public IList<int> Courses { get; set; }
            public static implicit operator StudentDTO(Student student)
            {
                return PropertyCopy<StudentDTO>.CopyFrom(student);
            }
        }


        static void Main(string[] args)
        {
            Student _student = new Student();
            _student.Id = 1;
            _student.Name = "Timmmmmmmmaaaahhhh";
            _student.Courses = new List<int>();
            _student.Courses.Add(101);
            _student.Courses.Add(121);

            StudentDTO itemT = _student;

            Console.WriteLine(itemT.Id);
            Console.WriteLine(itemT.Name);
            Console.WriteLine(itemT.Courses.Count);
        }


    }


    // COOLEST PIECE OF CODE FROM - http://www.yoda.arachsys.com/csharp/miscutil/

    /// <summary>
    /// Generic class which copies to its target type from a source
    /// type specified in the Copy method. The types are specified
    /// separately to take advantage of type inference on generic
    /// method arguments.
    /// </summary>
    public class PropertyCopy<TTarget> where TTarget : class, new()
    {
        /// <summary>
        /// Copies all readable properties from the source to a new instance
        /// of TTarget.
        /// </summary>
        public static TTarget CopyFrom<TSource>(TSource source) where TSource : class
        {
            return PropertyCopier<TSource>.Copy(source);
        }

        /// <summary>
        /// Static class to efficiently store the compiled delegate which can
        /// do the copying. We need a bit of work to ensure that exceptions are
        /// appropriately propagated, as the exception is generated at type initialization
        /// time, but we wish it to be thrown as an ArgumentException.
        /// </summary>
        private static class PropertyCopier<TSource> where TSource : class
        {
            private static readonly Func<TSource, TTarget> copier;
            private static readonly Exception initializationException;

            internal static TTarget Copy(TSource source)
            {
                if (initializationException != null)
                {
                    throw initializationException;
                }
                if (source == null)
                {
                    throw new ArgumentNullException("source");
                }
                return copier(source);
            }

            static PropertyCopier()
            {
                try
                {
                    copier = BuildCopier();
                    initializationException = null;
                }
                catch (Exception e)
                {
                    copier = null;
                    initializationException = e;
                }
            }

            private static Func<TSource, TTarget> BuildCopier()
            {
                ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource), "source");
                var bindings = new List<MemberBinding>();
                foreach (PropertyInfo sourceProperty in typeof(TSource).GetProperties())
                {
                    if (!sourceProperty.CanRead)
                    {
                        continue;
                    }
                    PropertyInfo targetProperty = typeof(TTarget).GetProperty(sourceProperty.Name);
                    if (targetProperty == null)
                    {
                        throw new ArgumentException("Property " + sourceProperty.Name + " is not present and accessible in " + typeof(TTarget).FullName);
                    }
                    if (!targetProperty.CanWrite)
                    {
                        throw new ArgumentException("Property " + sourceProperty.Name + " is not writable in " + typeof(TTarget).FullName);
                    }
                    if (!targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
                    {
                        throw new ArgumentException("Property " + sourceProperty.Name + " has an incompatible type in " + typeof(TTarget).FullName);
                    }
                    bindings.Add(Expression.Bind(targetProperty, Expression.Property(sourceParameter, sourceProperty)));
                }
                Expression initializer = Expression.MemberInit(Expression.New(typeof(TTarget)), bindings);
                return Expression.Lambda<Func<TSource,TTarget>>(initializer, sourceParameter).Compile();
            }
        }
    }

}
Eringo answered 10/2, 2009 at 9:37 Comment(0)
S
5

FYI

When I was having the same question I found AutoMapper (http://automapper.codeplex.com/) Then after reading AboutDev's answer i was done some simple test, the results pretty impressive

here the test results:

Test Auto Mapper:22322 ms

Test Implicit Operator:310 ms

Test Property Copy:250 ms

Test Emit Mapper:281 ms

And i would like to underline that it is sample only with classes(StudentDTO, Student) which have only a couple of properties, but what would happened if classes would have 50 - 100 properties, i guess it will affect performance dramatically.

More tests details here: Object copy approaches in .net: Auto Mapper, Emit Mapper, Implicit Operation, Property Copy

Segmental answered 9/8, 2010 at 14:17 Comment(2)
Without any code this test is pretty useless as it´s impossible to reproduce it.Barrelchested
@HimBromBeere the test code is here #3458157Segmental
S
4

Write a implicit operator in anyone class

    public static implicit operator StudentDTO(Student student)
    {

        //use skeet's library

        return PropertyCopy<StudentDTO>.CopyFrom(student);

    }

now you can do that

StudentDTO studentDTO = student;
Shoddy answered 10/2, 2009 at 9:23 Comment(0)
C
0

There's a library for doing just that - http://emitmapper.codeplex.com/

It's much faster than AutoMapper, it uses System.Reflection.Emit, so the code runs almost as fast as if it was hand-written.

Characharabanc answered 10/8, 2010 at 7:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.