Подтвердить что ты не робот

Применить значения свойств от одного объекта к другому того же типа автоматически?

Учитывая 2 объекта A и B типа T, я хочу присвоить значения свойств в тем же свойствам в B, не делая явное назначение для каждого свойства.

Я хочу сохранить код следующим образом:

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

делать что-то вроде

a.ApplyProperties(b);

Возможно ли это?

4b9b3361

Ответ 1

У меня есть тип 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 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

Ответ 2

Потому что я считаю, что версия Jon слишком сложна, и версия Стива слишком проста, и мне нравится идея Дэниэла о классе расширения.

Плюс общая версия довольно, но не нужна, поскольку все элементы являются объектами.

Я хотел бы добровольно назвать свою скудную и среднюю версию. Кредиты на все вышеперечисленное.: 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);
        }
    }
}

Ответ 3

Построение метода Стива, я пошел с использованием метода расширения. Это использует мой базовый класс как тип, но его можно использовать, даже используя объект в качестве типов параметров. Отлично работает для моих целей.

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.

Ответ 4

Вот короткая и приятная версия, так как вы сказали, что оба ваших объекта одного типа:

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

Ответ 5

Изменение версии Daniel для исключения исключений.

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

Ответ 6

Вы можете использовать сериализацию для глубокого клонирования объекта:

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, конечно.

Ответ 7

Там ICloneable и object.MemberwiseClone (мелкая копия) (они создают совершенно новый объект, поэтому могут не соответствовать вашим требованиям).

Вы можете использовать отражение, чтобы сделать это самостоятельно (наследовать от базового класса, чтобы вам не пришлось повторно реализовывать).

Или вы можете его генерировать.

Ответ 8

Вы можете попробовать что-то вроде этого....

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

Ответ 9

Этот короткий и простой метод Extension позволит вам копировать совпадающие свойства из одного объекта в другой с проверкой значения 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);
        }
    }

Ответ 10

Уже несколько лет я пользуюсь популярной библиотекой под названием 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

Обратите внимание, что даже несмотря на то, что базовые решения довольно просты (см. Другие ответы), существует множество граничных случаев (например, глубокое копирование, универсальные значения, нулевые значения) и оптимизаций (например, кэширование отраженных свойств), поэтому лучше использовать для этого поддерживаемую библиотеку.

Ответ 11

В основном в 2019 году нам, вероятно, следует использовать более современные языковые функции, такие как деревья выражений и скомпилированные лямбда-выражения, вместо Reflection

Так как я не смог найти "мелкого клонера", отвечающего моим требованиям (скорость больше всего), я решил создать его сам. Он перечисляет все свойства gettable/settable, а затем создает выражение Block которое затем компилируется и кэшируется. Что делает его почти в 13 раз быстрее, чем популярный AutoMapper. Использование очень просто:

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

Вы можете просмотреть полный исходный код здесь: https://jitbit.github.io/PropMapper/

Ответ 12

Если вы хотите что-то вроде ApplyProperties, вы можете написать метод расширения для Object, который будет делать то, что вам нужно. Просто поймите, что такой метод расширения не будет "чистым" или свободным от побочных эффектов. Но если вам нужна возможность, это способ ее достижения.

Ответ 13

public static void GatherFrom<TSelf, TSource>(this TSelf self, TSource source)
{
    PropertyInfo[] sourceAllProperties = source.GetType().GetProperties(); 

    foreach (PropertyInfo sourceProperty in sourceAllProperties)
    {
        PropertyInfo selfProperty = self.GetType().GetProperty(sourceProperty.Name);
        if (selfProperty != null)
        {
            var sourceValue = sourceProperty.GetValue(source, null);
            selfProperty.SetValue(self, sourceValue, null);                       
        }
    }
}

Ответ 14

Возможно, этот пост немного устарел, ответы хороши, но когда вам нужно сопоставить множество объектов с целевым типом - цикл через свойства может быть дорогостоящим (Imaging 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
...

Ответ 15

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

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