将属性值从一个对象自动应用于另一个相同类型的对象?


78

给定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);

可能吗?


是否有不使用反射的解决方案?
Tharindu Sathischandra

Answers:


75

我有一个MiscUtil叫作PropertyCopy类似操作的类型-尽管它创建了目标类型的新实例并将属性复制到其中。

它不需要类型相同-只是将所有可读属性从“源”类型复制到“目标”类型。当然,如果类型相同,则更可能起作用:)这是一个浅表副本,顺便说一句。

在此答案底部的代码块中,我扩展了类的功能。要从一个实例复制到另一个实例,它PropertyInfo在执行时使用简单的值-这比使用表达式树要慢,但是另一种选择是编写一个动态方法,我不太喜欢这种方法。如果性能对您绝对至关重要,请告诉我,我会解决的。要使用该方法,请编写如下内容:

MyType instance1 = new MyType();
// Do stuff
MyType instance2 = new MyType();
// Do stuff

PropertyCopy.Copy(instance1, instance2);

(这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

如果目标没有自己的构造函数,则复制到现有目标时将失败,例如IMyType instance2 = new MyType();。这是不可避免的吗?
stovroz

@stovroz:好吧,您必须提供您实际想要实例化的类型。
乔恩·斯基特

@Jon:但是目标已经实例化了。我认为您的上述增强功能完全是为了允许复制到现有目标,但无论如何似乎还是要创建一个新对象:Expression.MemberInit(Expression.New(typeof(TTarget)),bindings)。
stovroz 2010年

@Jon:我有一个包含List <>属性的类,但是它不会创建List <>的新实例,因此,如果我向列表中添加内容,它将同时添加到属性(源和目标)中。也可以在属性中创建引用类型的新实例。只是一个建议
而已

2
@Cawas:啊,对不起-我正在看Stack Overflow术语中的注释:)我想给它起一个名词名称,例如PropertyCopier。老实说,我不得不多考虑一些事情,并查看一些用例来正确地重新设计它。
乔恩·斯基特

84

因为我相信Jon的版本有点太复杂了,而Steve的版本太简单了,所以我喜欢Daniel的扩展类思想。

再加上通用版本很漂亮,但不必要,因为所有项目都是对象。

我想自愿提供我的精瘦版本。归功于以上所有内容。:D

码:

using System;
using System.Reflection;
/// <summary>
/// A static class for reflection type functions
/// </summary>
public static class Reflection
{
    /// <summary>
    /// Extension for 'Object' that copies the properties to a destination object.
    /// </summary>
    /// <param name="source">The source.</param>
    /// <param name="destination">The destination.</param>
    public static void CopyProperties(this object source, object destination)
    {
        // If any this null throw an exception
        if (source == null || destination == null)
            throw new Exception("Source or/and Destination Objects are null");
            // Getting the Types of the objects
        Type typeDest = destination.GetType();
        Type typeSrc = source.GetType();

        // Iterate the Properties of the source instance and  
        // populate them from their desination counterparts  
        PropertyInfo[] srcProps = typeSrc.GetProperties();
        foreach (PropertyInfo srcProp in srcProps)
        {
            if (!srcProp.CanRead)
            {
                continue;
            }
            PropertyInfo targetProperty = typeDest.GetProperty(srcProp.Name);
            if (targetProperty == null)
            {
                continue;
            }
            if (!targetProperty.CanWrite)
            {
                continue;
            }
            if (targetProperty.GetSetMethod(true) != null && targetProperty.GetSetMethod(true).IsPrivate)
            {
                continue;
            }
            if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0)
            {
                continue;
            }
            if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType))
            {
                continue;
            }
            // Passed all tests, lets set the value
            targetProperty.SetValue(destination, srcProp.GetValue(source, null), null);
        }
    }
}

用法:

/// <summary>
/// ExampleCopyObject
/// </summary>
/// <returns></returns>
public object ExampleCopyObject()
{
    object destObject = new object();
    this.CopyProperties(destObject); // inside a class you want to copy from

    Reflection.CopyProperties(this, destObject); // Same as above but directly calling the function

    TestClass srcClass = new TestClass();
    TestStruct destStruct = new TestStruct();
    srcClass.CopyProperties(destStruct); // using the extension directly on a object

    Reflection.CopyProperties(srcClass, destObject); // Same as above but directly calling the function

    //so on and so forth.... your imagination is the limits :D
    return srcClass;
}

public class TestClass
{
    public string Blah { get; set; }
}
public struct TestStruct
{
    public string Blah { get; set; }
}

我很无聊,并通过评论建议了linq版本

using System;
using System.Linq;
using System.Reflection;
/// <summary>
/// A static class for reflection type functions
/// </summary>
public static class Reflection
{
    /// <summary>
    /// Extension for 'Object' that copies the properties to a destination object.
    /// </summary>
    /// <param name="source">The source.</param>
    /// <param name="destination">The destination.</param>
    public static void CopyProperties(this object source, object destination)
    {
        // If any this null throw an exception
        if (source == null || destination == null)
            throw new Exception("Source or/and Destination Objects are null");
        // Getting the Types of the objects
        Type typeDest = destination.GetType();
        Type typeSrc = source.GetType();
        // Collect all the valid properties to map
        var results = from srcProp in typeSrc.GetProperties()
                                    let targetProperty = typeDest.GetProperty(srcProp.Name)
                                    where srcProp.CanRead
                                    && targetProperty != null
                                    && (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
                                    && (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
                                    && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
                                    select new { sourceProperty = srcProp, targetProperty = targetProperty };
        //map the properties
        foreach (var props in results)
        {
            props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), null);
        }
    }
}

哈哈,当我向下滚动到您的答案时,我确实在想着同样的想法。Goldilocks解决方案。虽然,如果我要写一个,肯定会使用Linq :)
theMayer 2013年

我收到一个空异常。我认为这与只读属性有关。我该如何避免呢?
Pedro77 2014年

好的,在下面添加了代码:“(targetProperty.GetSetMethod(true)!= null &&!targetProperty.GetSetMethod(true).IsPrivate)”
Pedro77 2014年

23
对于乔恩·斯凯特(Jon Skeet)也回答的问题,应该有一个获得20票的答案的徽章。
托尼L.

为什么选择“!targetProperty.GetSetMethod(true).IsPrivate”?
Rhyous

29

这是一个简短而有趣的版本,因为您说过两个对象都属于同一类型:

foreach (PropertyInfo property in typeof(YourType).GetProperties().Where(p => p.CanWrite))
{
    property.SetValue(targetObject, property.GetValue(sourceObject, null), null);
}

22

在史蒂夫方法的基础上,我采用了扩展方法方法。这使用我的基类作为类型,但是即使使用object作为参数类型也应该可用。非常适合我使用。

using System.Reflection;
//*Namespace Here*
public static class Ext
{
    public static void CopyProperties(this EntityBase source, EntityBase destination)
    {
        // Iterate the Properties of the destination instance and  
        // populate them from their source counterparts  
        PropertyInfo[] destinationProperties = destination.GetType().GetProperties(); 
        foreach (PropertyInfo destinationPi in destinationProperties)
        {
            PropertyInfo sourcePi = source.GetType().GetProperty(destinationPi.Name);     
            destinationPi.SetValue(destination, sourcePi.GetValue(source, null), null);
        } 
    }
}

用法如下所示:

item1.CopyProperties(item2);

现在,Item2具有与item1相同的属性数据。


5
如果其中一个属性为只读,则以上代码将中断。在SetValue之前添加一个检查:if(destinationPi.CanWrite),以避免引发异常。
J Wynia

7

丹尼尔版本的修改以避免例外。

foreach (PropertyInfo property in typeof(YourType).GetProperties())
{
  if (property.CanWrite)
  {
    property.SetValue(marketData, property.GetValue(market, null), null);
  }
}

4

这个简短而简单的扩展方法将使您可以通过检查Null值将匹配的属性从一个对象复制到另一个对象,并且可写。

public static void CopyPropertiesTo(this object fromObject, object toObject)
    {
        PropertyInfo[] toObjectProperties = toObject.GetType().GetProperties();
        foreach (PropertyInfo propTo in toObjectProperties)
        {
            PropertyInfo propFrom = fromObject.GetType().GetProperty(propTo.Name);
            if (propFrom!=null && propFrom.CanWrite)
                propTo.SetValue(toObject, propFrom.GetValue(fromObject, null), null);
        }
    }

4

基本上在2019年,我们可能应该使用更多最新的语言功能,例如表达式树和已编译的lambda表达式,而不是Reflection

由于找不到符合我要求的“浅克隆器”(最重要的是速度),我决定自己创建一个。它枚举所有gettable / settable属性,然后创建一个Block表达式,然后对其进行编译和缓存。这使其比流行的AutoMapper快13倍。用法很简单:

DestType destObject = PropMapper<SourceType, DestType>.From(srcObj);

您可以在此处查看完整的源代码:https : //github.com/jitbit/PropMapper


1
那里的MIT许可证实在让人不知所措..对于小的功能,必须捆绑和维护整个许可证。
最多

@Max我不是很擅长此事,我应该选择哪个许可证?我一直以为麻省理工学院是最简单,最宽松的(就像“随便做什么”一样)
Alex

这是最简单,最宽松的。您要做的就是在源代码中包含许可证,这并不困难。
nelsontruran

什么是名称空间?VS是通过Nuget安装的,VS无法使用statement读取,并且自述文件也没有这样说。
帕维尔

@Pawel这是“ Jitbit.Utils”,但是您可以检查源代码;)
Alex

3

您可以使用序列化来深克隆对象:

public static T DeepClone<T>(this T objectToClone) where T: BaseClass
{
    BinaryFormatter bFormatter = new BinaryFormatter();
    MemoryStream stream = new MemoryStream();
    bFormatter.Serialize(stream, objectToClone);
    stream.Seek(0, SeekOrigin.Begin);
    T clonedObject = (T)bFormatter.Deserialize(stream);
    return clonedObject;
}

当然,只需要将类标记为Serializable。


另外,如果您想添加对JSON.NET的依赖关系以支持对的依赖Serializable,则可以序列化和反序列化为JSON
KyleMit

如果您只想更改属性值,这将创建一个新实例,这可能不是您打算执行的操作。
Matthieu


2

几年来,我一直使用一个流行的库命名为ValueInjecter

nuget:https://www.nuget.org/packages/ValueInjecter/

github的:https : //github.com/omuleanu/ValueInjecter

target.InjectFrom(source);
target.InjectFrom<Injection>(source);
target.InjectFrom(new Injection(parameters), source);
target.InjectFrom<Injection>(); // without source

请注意,即使基本解决方案非常简单(请参见其他答案),也有很多边缘情况(例如,深拷贝,泛型,空值)和优化(例如,缓存反映的属性),因此更好地使用维护的库。


1

您可以尝试这样的事情。

MyType destination = new MyType();
MyType source = new MyType();

// Iterate the Properties of the destination instance and 
// populate them from their source counterparts

PropertyInfo[] destinationProperties = destination.GetType().GetProperties();
foreach (PropertyInfo destinationPI in destinationProperties)
{
    PropertyInfo sourcePI = source.GetType().GetProperty(destinationPI.Name);

    destinationPI.SetValue(destination,
                           sourcePI.GetValue(source, null), 
                           null);
}

1

也许这篇文章有点老了,答案还是不错的,但是当您需要将许多对象映射到目标类型中时-遍历属性可能会很昂贵(想象100个道具及更多)。

我使用此AutoMapFactory方法(仅需要LinQ),它将仅在属性中迭代一次,并且每个对象的映射将非常快(100000个道具/秒)

private Func<S,T> AutoMapFactory<S,T>() where T: class, new() where S : class
        {
            List<Action<T, S>> mapActions = typeof(T).GetProperties().Where(tp => tp.CanWrite)
                .SelectMany(tp => typeof(S).GetProperties().Where(sp => sp.CanRead)
                .Where(sp => sp.Name == tp.Name && tp.PropertyType.IsAssignableFrom(sp.PropertyType))
                .Select(sp => (Action<T,S>)((targetObj, sourceObj) => 
                    tp.SetValue(targetObj, sp.GetValue(sourceObj)))))
                .ToList();

            return sourceObj => {
                if (sourceObj == null) return null;

                T targetObj = new T();
                mapActions.ForEach(action => action(targetObj, sourceObj));
                return targetObj;
            };
        }

使用方法:

...
var autoMapper = AutoMapFactory<SourceType, TargetType>(); //Get Only 1 instance of the mapping function
...
someCollection.Select(item => autoMapper(item)); //Almost instantaneous
...

0

如果您想要诸如ApplyProperties之类的东西,则可以在Object上编写一个扩展方法,该方法可以满足您的需求。只要意识到这样的扩展方法就不会“纯粹”或无副作用。但是,如果您需要此功能,则可以通过一种方法来实现。


0

扩展@Azerothians答案,我需要一些其他要求:

  1. 需要一种忽略源对象上的空属性的方法
  2. 需要一种忽略显式属性的方法
  3. 想要在可能的情况下强制转换数据类型的方法,例如int?到小数?或很长一段时间(由于前辈不幸的早期架构决定)
  4. 想知道属性是否已复制(以确定是否需要更新数据库)

    /// <summary>
    /// Extension for 'Object' that copies the properties to a destination object.
    /// </summary>
    /// <param name="source">The source.</param>
    /// <param name="destination">The destination.</param>
    /// <param name="PropertiesToIgnore">an optional list of property names which will NOT be copied</param>
    /// <param name="IgnoreNullProperties">when true will not update properties where the source is null</param>
    /// <param name="CoerceDataType">when true, will attempt to coerce the source property to the destination property (e.g. int to decimal) </param>
    /// <param name="ThrowOnTypeMismatch">when true, will throw a InvalidCastException if the data cannot be coerced</param>
    /// <exception cref="InvalidCastException">if there is a data type mismatch between source/destination and ThrowOnTypeMismatch is enabled and unable to coerce the data type.</exception>
    /// <returns>true if any properties were changed</returns>
    public static bool CopyProperties(this object source, object destination, IEnumerable<string> PropertiesToIgnore = null, bool IgnoreNullProperties = false, bool ThrowOnTypeMismatch = true, bool CoerceDataType = true)
    {
        if (source is null)
            throw new ArgumentNullException(nameof(source));
    
        if (destination is null)
            throw new ArgumentNullException(nameof(destination));
    
        // Getting the Types of the objects
        Type typeDest = destination.GetType();
        Type typeSrc = source.GetType();
    
        // Collect all the valid properties to map
        var results = (from srcProp in typeSrc.GetProperties()
                       let targetProperty = typeDest.GetProperty(srcProp.Name)
                       where srcProp.CanRead
                       && targetProperty != null
                       && (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
                       && (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
                       && !(
                           from i in PropertiesToIgnore ?? Enumerable.Empty<string>()
                           select i
                         ).Contains(srcProp.Name)
                       && (!IgnoreNullProperties || srcProp.GetValue(source, null) != null)
                       select new { sourceProperty = srcProp, targetProperty = targetProperty }).ToList();
    
        bool PropertyChanged = false;
        //map the properties
        foreach (var props in results)
        {
            var srcValue = props.sourceProperty.GetValue(source, null);
            var dstValue = props.targetProperty.GetValue(destination, null);
            if (props.targetProperty.PropertyType.IsAssignableFrom(props.sourceProperty.PropertyType))
                props.targetProperty.SetValue(destination, srcValue, null);
            else
            {
                try
                {
                    if (!CoerceDataType)
                        throw new InvalidCastException($"Types do not match, source: {props.sourceProperty.PropertyType.FullName}, target: {props.targetProperty.PropertyType.FullName}.");
    
                    if (srcValue != null)
                    {
                        // determine if nullable type
                        Type tgtType = Nullable.GetUnderlyingType(props.targetProperty.PropertyType);
                        // if it is, use the underlying type
                        // without this we cannot convert int? -> decimal? when value is not null
                        if (tgtType != null)
                            props.targetProperty.SetValue(destination, Convert.ChangeType(srcValue, tgtType, CultureInfo.InvariantCulture), null);
                        else // otherwise use the original type
                            props.targetProperty.SetValue(destination, Convert.ChangeType(srcValue, props.targetProperty.PropertyType, CultureInfo.InvariantCulture), null);
                    }
                    else // if null we can just set it as null
                        props.targetProperty.SetValue(destination, null, null);
                }
                catch (Exception ex)
                {
                    if (ThrowOnTypeMismatch)
                        throw new InvalidCastException($"Unable to copy property {props.sourceProperty.Name} with value {srcValue} from object of type ({typeSrc.FullName}) to type ({typeDest.FullName}), Error: {ex.Message}");
                    // else ignore update
                }
                var newdstValue = props.targetProperty.GetValue(destination, null);
                if (newdstValue == null && dstValue != null || !newdstValue.Equals(dstValue))
                    PropertyChanged = true;
            }
        }
    
        return PropertyChanged;
    }
    

-3
public TestClass {
    public TestName {get;set;}
}
public void submain()
{
    var originalTestClass = new TestClass()
    {
        TestName  ="Test Name";
    };

    var newTestClass = new TestClass();
     newTestClass.CopyPropertiesFrom(originalTestClass);
}

3
请描述问题所在,以及如何解决此片段,以帮助将来的读者理解此答案。而且,任务已经超过10年了……
FZ
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.