【问题标题】:How to copy value from class X to class Y with the same property name in c#?如何在c#中将值从X类复制到具有相同属性名的Y类?
【发布时间】:2009-02-10 08:40:21
【问题描述】:

假设我有两个类:

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;

如何通过反思或其他解决方案做到这一点?

【问题讨论】:

  • 看看Automapper。该工具旨在处理这种确切的情况。

标签: c# .net reflection


【解决方案1】:

列表让它变得棘手......我之前的回复(如下)仅适用于同类属性(而不是列表)。我怀疑您可能只需要编写和维护代码:

    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 将其构建为一段已编译的代码。 Jon Skeet 在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));
}

【讨论】:

  • 非常快...我只是在打字并收到一条错误消息,说有新答案及其...所以我中止了:(
  • 您对 CourseDTO 列表有任何问题吗?因为 CourseDTO 可能与 Course 不同
  • Marc,非常棒的 MiscUtil 链接。这段代码太优雅了。我希望你不介意我把它贴在这里,因为人们真的应该看到它。我正在修改你的答案,伙计!该链接是一个很棒的发现。
  • 哦,MiscUtil 对我的 CourseDTO 列表没有帮助。其他任何解决方案?因为我的班级有很多属性和子列表。
  • 也许可以让表达式为子级递归调用相同的表达式方法......问题是由于限制,在此过程中需要一些“流畅”的包装器在 3.5 中的 Expression 内(在 4.0 中修复)。
【解决方案2】:

好的,我刚刚查看了 Marc 发布的 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();
            }
        }
    }

}

【讨论】:

    【解决方案3】:

    仅供参考

    当我遇到同样的问题时,我发现了 AutoMapper (http://automapper.codeplex.com/) 然后在阅读了 AboutDev 的答案后,我做了一些简单的测试,结果令人印象深刻

    这里是测试结果:

    测试自动映射器:22322 毫秒

    测试隐式运算符:310 毫秒

    测试属性复制:250 毫秒

    测试发射映射器:281 毫秒

    我想强调的是,它只是类(StudentDTO,Student)的样本,它们只有几个属性,但是如果类有 50-100 个属性会发生什么,我想它会显着影响性能。

    更多测试细节在这里: Object copy approaches in .net: Auto Mapper, Emit Mapper, Implicit Operation, Property Copy

    【讨论】:

    【解决方案4】:

    在任何类中编写隐式运算符

        public static implicit operator StudentDTO(Student student)
        {
    
            //use skeet's library
    
            return PropertyCopy<StudentDTO>.CopyFrom(student);
    
        }
    

    现在你可以这样做了

    StudentDTO studentDTO = student;
    

    【讨论】:

      【解决方案5】:

      有一个库可以做到这一点 - http://emitmapper.codeplex.com/

      它比 AutoMapper 快得多,它使用 System.Reflection.Emit,因此代码的运行速度几乎与手写代码一样快。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-09-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-03
        相关资源
        最近更新 更多