代码之家  ›  专栏  ›  技术社区  ›  eKek0

是否自动将属性值从一个对象应用到同一类型的另一个对象?

  •  64
  • eKek0  · 技术社区  · 15 年前

    给定两个类型为T的对象A和B,我想将A中的属性值赋给B中的相同属性,而不必对每个属性进行显式赋值。

    我想像这样保存代码:

    b.Nombre = a.Nombre;
    b.Descripcion = a.Descripcion;
    b.Imagen = a.Imagen;
    b.Activo = a.Activo;
    

    a.ApplyProperties(b);
    

    可能吗?

    11 回复  |  直到 7 年前
        1
  •  91
  •   Azerothian    7 年前

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

    代码:

    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);
            }
        }
    }
    
        2
  •  78
  •   Mark Amery Harley Holcombe    7 年前

    我有打字 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
    
        3
  •  39
  •   Shimmy Weitzhandler 500 - Internal Server Error    5 年前

    这是一个简短而甜蜜的版本,因为你说你的两个对象是同一类型的:

    foreach (PropertyInfo property in typeof(YourType).GetProperties().Where(p => p.CanWrite))
    {
        property.SetValue(targetObject, property.GetValue(sourceObject, null), null);
    }
    
        4
  •  26
  •   Daniel    13 年前

    基于Steve的方法,我采用了扩展方法。 它使用我的基类作为类型,但即使使用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
  •  9
  •   arteny    7 年前

    foreach (PropertyInfo property in typeof(YourType).GetProperties())
    {
      if (property.CanWrite)
      {
        property.SetValue(marketData, property.GetValue(market, null), null);
      }
    }
    
        6
  •  8
  •   Ashdeep Singh    6 年前

    这个简短的扩展方法允许您通过检查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);
            }
        }
    
        7
  •  5
  •   meum Alex from Jitbit    4 年前

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

    由于我无法找到一个“浅克隆器”来满足我的要求(最重要的是速度),我决定自己创建一个。它枚举所有可获取/可设置的属性,然后创建 Block

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

    https://github.com/jitbit/PropMapper

        8
  •  3
  •   KyleMit Steven Vachon    7 年前

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

    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;
    }
    

    当然,类必须标记为可序列化。

        9
  •  3
  •   Aviko    6 年前

    好几年来,我一直在使用一个流行的图书馆 估价师

    努吉: 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
    

    请注意,即使基本解决方案非常简单(请参阅其他答案),也存在大量的边缘情况(例如深度复制、泛型、空值)和优化(例如缓存反射的属性),因此最好使用维护库来实现这一点。

        10
  •  2
  •   Cade Roux    15 年前

    ICloneable object.MemberwiseClone

    或者你可以用代码生成它。

        11
  •  1
  •   Steve    13 年前

    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);
    }
    
        12
  •  1
  •   Jonathan Larouche    5 年前

    我使用这个AutomaFactory方法(只需要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
    ...
    
        13
  •  0
  •   jrista    15 年前

    若您想要像ApplyProperty这样的东西,您可以在对象上编写一个扩展方法,该方法可以满足您的需要。只要意识到这样的扩展方法不是“纯粹的”,也不是没有副作用的。但是如果你需要这种能力,这是一种实现它的方法。

        14
  •  0
  •   Justin    4 年前

    扩展@Azerothians answer,我需要一些额外的要求:

    1. 需要一种忽略显式属性的方法
    2. 想要一种在可能的情况下强制数据类型匹配的方法,例如int?到小数点?或long to int(由于前一个架构的早期错误决策)
    3. /// <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;
      }
      
        15
  •  0
  •   Patrick    3 年前

    我已经采纳了@Shimmy Weitzhandler上面写的所有内容,并进行了轻微修改。由于我在必须定义具有相同属性的多个对象时使用了此方法,因此我创建了一个接受可变数量元素的函数:

    public static void CopyProperties<T>(T source, params T[] dests)
    {
        if(source == null)
            return;
    
        foreach (var dest in dests)
        {
            foreach (System.Reflection.PropertyInfo property in typeof(Button).GetProperties().Where(p => p.CanWrite))
                property.SetValue(dest, property.GetValue(source, null), null);
        }
    }
    

    例如。:

    Helper.CopyProperties(btn_Src, bt_Dest1);
    

    Helper.CopyProperties(btn_Src, bt_Dest1, btn_Dest2, btn_Dest3);
    

    我将其设置为静态,因为它位于助手库中

        16
  •  -3
  •   Malik Waqas    5 年前
    public TestClass {
        public TestName {get;set;}
    }
    public void submain()
    {
        var originalTestClass = new TestClass()
        {
            TestName  ="Test Name";
        };
    
        var newTestClass = new TestClass();
         newTestClass.CopyPropertiesFrom(originalTestClass);
    }