Как скопировать значение из класса X в класс Y с тем же именем свойства в C#?
Предположим, у меня есть два класса:
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;}
}
Я хотел бы скопировать значение из класса Student в класс StudentDTO:
var student = new Student();
StudentDTO studentDTO = student;
Как я могу сделать это с помощью отражения или другого решения?
5 ответов
Списки усложняют... Мой предыдущий ответ (ниже) относится только к подобным свойствам (но не к спискам). Я подозреваю, что вам, возможно, просто нужно написать и поддерживать код:
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
});
}
редактировать; применяется только к мелким копиям - не списки
Отражение - вариант, но медленный. В 3.5 вы можете встроить это в скомпилированный фрагмент кода с Expression
, У Джона Скита есть предварительно подготовленный образец этого в MiscUtil - просто используйте как:
Student source = ...
StudentDTO item = PropertyCopy<StudentDTO>.CopyFrom(student);
Потому что это использует скомпилированный Expression
это значительно превзойдет результаты рефлексии.
Если у вас нет 3.5, используйте рефлексию или ComponentModel. Если вы используете ComponentModel, вы можете по крайней мере использовать HyperDescriptor
чтобы получить почти так же быстро, как 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));
}
Хорошо, я только что посмотрел MiscUtil, о котором писал Марк, и он просто потрясающий. Надеюсь, Марк не против, чтобы я добавил код здесь.
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();
}
}
}
}
FYI
Когда у меня возник тот же вопрос, я обнаружил, что AutoMapper ( http://automapper.codeplex.com/) Затем, прочитав ответ AboutDev, провел простой тест, результаты которого довольно впечатляющие.
вот результаты теста:
Тест Авто картограф:22322 мс
Тест неявного оператора:310 мс
Копия тестового свойства:250 мс
Тест Emit Mapper:281 мс
И я хотел бы подчеркнуть, что это образец только с классами (StudentDTO, Student), которые имеют только пару свойств, но что произойдет, если у классов будет 50–100 свойств, я думаю, это сильно повлияет на производительность.
Подробнее о тестах здесь: Подходы к копированию объектов в.net: Auto Mapper, Emit Mapper, неявная операция, копирование свойств
Написать неявный оператор в любом классе
public static implicit operator StudentDTO(Student student)
{
//use skeet's library
return PropertyCopy<StudentDTO>.CopyFrom(student);
}
теперь вы можете сделать это
StudentDTO studentDTO = student;
Для этого есть библиотека - http://emitmapper.codeplex.com/
Это намного быстрее, чем AutoMapper, он использует System.Reflection.Emit, поэтому код работает почти так же быстро, как если бы он был написан от руки.