Копирование значений из одного объекта в другой

У кого-нибудь есть предложение для хорошего класса утилиты, который отображает значения из одного объекта в другой? Я хочу, чтобы класс утилиты использовал отражение и берет два объекта и копирует значения от первого объекта ко второму, если есть общедоступное свойство с тем же именем.

У меня есть два объекта, которые генерируются из прокси-сервера веб-службы, поэтому я не могу изменить родительский класс или использовать интерфейс или что-то в этом роде. Но я знаю, что эти два объекта имеют одинаковые общедоступные свойства.

Ответ 1

У Джона Скита и Марка Гравелла есть библиотека под названием MiscUtil. Внутри MiscUtil.Reflection есть класс под названием PropertyCopy, который делает именно то, что вы описываете. Он работает только для .NET 3.5.

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

Что, эй, я подумал, что просто опубликую их краткий код (это менее 100 строк с комментариями). Лицензию на этот код можно найти здесь:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace MiscUtil.Reflection
{
    /// <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>.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();
            }
        }
    }
}

Ответ 2

Должно быть довольно просто бросить вместе...

public static void CopyPropertyValues(object source, object destination)
{
    var destProperties = destination.GetType().GetProperties();

    foreach (var sourceProperty in source.GetType().GetProperties())
    {
        foreach (var destProperty in destProperties)
        {
            if (destProperty.Name == sourceProperty.Name && 
        destProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
            {
                destProperty.SetValue(destination, sourceProperty.GetValue(
                    source, new object[] { }), new object[] { });

                break;
            }
        }
    }
}

Ответ 3

Мы используем Automapper. Он работает очень хорошо.

Ответ 4

Я улучшил ответ Робинсона и реорганизовал его в метод расширения для типа объекта, очень удобный:

    public static void CopyPropertyValues( this object destination, object source )
    {
        if ( !( destination.GetType ().Equals ( source.GetType () ) ) )
            throw new ArgumentException ( "Type mismatch" );
        if ( destination is IEnumerable )
        {
            var dest_enumerator = (destination as IEnumerable).GetEnumerator();
            var src_enumerator = (source as IEnumerable).GetEnumerator();
            while ( dest_enumerator.MoveNext () && src_enumerator.MoveNext () )
                dest_enumerator.Current.CopyPropertyValues ( src_enumerator.Current );
        }
        else
        {
            var destProperties = destination.GetType ().GetRuntimeProperties ();
            foreach ( var sourceProperty in source.GetType ().GetRuntimeProperties () )
            {
                foreach ( var destProperty in destProperties )
                {
                    if ( destProperty.Name == sourceProperty.Name 
                        && destProperty.PropertyType.GetTypeInfo ()
                            .IsAssignableFrom ( sourceProperty.PropertyType.GetTypeInfo () ) )
                    {
                        destProperty.SetValue ( destination, sourceProperty.GetValue (
                            source, new object[] { } ), new object[] { } );
                        break;
                    }
                }
            }
        }
    }

Ответ 5

Я писал о используя Expression<T> для этого вскоре после чтения этой статьи от Марка Гравелла.

Он выглядит (на основе другого ответа), как будто он может быть похож на тот, который находится в Jon Skeet и Marc MiscUtil.

Ответ 6

Как это сделать с Json.net?

static T CopyPropertiesJson<T>(object source)
{
    string jsonsource = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(jsonsource);
}