给定2个类型T的对象A和B,我想将A中的属性值分配给B中的相同属性,而无需为每个属性进行显式分配。
我想保存这样的代码:
b.Nombre = a.Nombre; b.Descripcion = a.Descripcion; b.Imagen = a.Imagen; b.Activo = a.Activo;
做类似的事情
a.ApplyProperties(b);
可能吗?
我有一个MiscUtil叫作PropertyCopy类似操作的类型- 尽管它创建了目标类型的新实例并将属性复制到其中。
MiscUtil
PropertyCopy
它不需要类型相同-只是将所有可读属性从“源”类型复制到“目标”类型。当然,如果类型相同,那更可能起作用:)它是一个浅表副本,顺便说一句。
在此答案底部的代码块中,我扩展了类的功能。要从一个实例复制到另一个实例,它PropertyInfo在执行时使用简单的值- 这比使用表达式树要慢,但是另一种选择是编写一个动态方法,我不太喜欢这种方法。如果性能对您绝对至关重要,请让我知道,我会解决的。要使用该方法,请编写如下内容:
PropertyInfo
MyType instance1 = new MyType(); // Do stuff MyType instance2 = new MyType(); // Do stuff PropertyCopy.Copy(instance1, instance2);
(哪里Copy是使用类型推断的通用方法)。
Copy
我还没有准备好完整的MiscUtil版本,但是这里是更新的代码,包括注释。我不会为SO编辑器重新包装它们-只需复制整个块即可。
(如果我从头开始,我可能还会在命名方面对API进行一些重新设计,但我不想破坏现有的用户…)
#if DOTNET35 using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; namespace MiscUtil.Reflection { /// <summary> /// Non-generic class allowing properties to be copied from one instance /// to another existing instance of a potentially different type. /// </summary> public static class PropertyCopy { /// <summary> /// Copies all public, readable properties from the source object to the /// target. The target type does not have to have a parameterless constructor, /// as no new instance needs to be created. /// </summary> /// <remarks>Only the properties of the source and target types themselves /// are taken into account, regardless of the actual types of the arguments.</remarks> /// <typeparam name="TSource">Type of the source</typeparam> /// <typeparam name="TTarget">Type of the target</typeparam> /// <param name="source">Source to copy properties from</param> /// <param name="target">Target to copy properties to</param> public static void Copy<TSource, TTarget>(TSource source, TTarget target) where TSource : class where TTarget : class { PropertyCopier<TSource, TTarget>.Copy(source, target); } } /// <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 static 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, TTarget>.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. /// Note that this type we do not have a constructor constraint on TTarget, because /// we only use the constructor when we use the form which creates a new instance. /// </summary> internal static class PropertyCopier<TSource, TTarget> { /// <summary> /// Delegate to create a new instance of the target type given an instance of the /// source type. This is a single delegate from an expression tree. /// </summary> private static readonly Func<TSource, TTarget> creator; /// <summary> /// List of properties to grab values from. The corresponding targetProperties /// list contains the same properties in the target type. Unfortunately we can't /// use expression trees to do this, because we basically need a sequence of statements. /// We could build a DynamicMethod, but that's significantly more work :) Please mail /// me if you really need this... /// </summary> private static readonly List<PropertyInfo> sourceProperties = new List<PropertyInfo>(); private static readonly List<PropertyInfo> targetProperties = new List<PropertyInfo>(); private static readonly Exception initializationException; internal static TTarget Copy(TSource source) { if (initializationException != null) { throw initializationException; } if (source == null) { throw new ArgumentNullException("source"); } return creator(source); } internal static void Copy(TSource source, TTarget target) { if (initializationException != null) { throw initializationException; } if (source == null) { throw new ArgumentNullException("source"); } for (int i = 0; i < sourceProperties.Count; i++) { targetProperties[i].SetValue(target, sourceProperties[i].GetValue(source, null), null); } } static PropertyCopier() { try { creator = BuildCreator(); initializationException = null; } catch (Exception e) { creator = null; initializationException = e; } } private static Func<TSource, TTarget> BuildCreator() { ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource), "source"); var bindings = new List<MemberBinding>(); foreach (PropertyInfo sourceProperty in typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { 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.GetSetMethod().Attributes & MethodAttributes.Static) != 0) { throw new ArgumentException("Property " + sourceProperty.Name + " is static 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))); sourceProperties.Add(sourceProperty); targetProperties.Add(targetProperty); } Expression initializer = Expression.MemberInit(Expression.New(typeof(TTarget)), bindings); return Expression.Lambda<Func<TSource, TTarget>>(initializer, sourceParameter).Compile(); } } } #endif