web-dev-qa-db-fra.com

Appliquer automatiquement les valeurs des propriétés d'un objet à un autre du même type?

Étant donné 2 objets A et B de type T, je veux affecter les valeurs des propriétés dans A aux mêmes propriétés dans B sans faire une affectation explicite pour chaque propriété.

Je veux enregistrer du code comme ceci:

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

faire quelque chose comme

a.ApplyProperties(b);

C'est possible?

64
eKek0

J'ai un type dans MiscUtil appelé PropertyCopy qui fait quelque chose de similaire - bien qu'il crée une nouvelle instance du type cible et copie les propriétés dans celui-ci.

Il ne nécessite pas que les types soient identiques - il copie simplement toutes les propriétés lisibles du type "source" vers le type "cible". Bien sûr, si les types sont les mêmes, c'est plus susceptible de fonctionner :) C'est une copie superficielle, btw.

Dans le bloc de code au bas de cette réponse, j'ai étendu les capacités de la classe. Pour copier d'une instance à une autre, il utilise de simples valeurs PropertyInfo au moment de l'exécution - c'est plus lent que d'utiliser un arbre d'expression, mais l'alternative serait d'écrire une méthode dynamique, sur laquelle je ne suis pas trop chaud . Si les performances sont absolument essentielles pour vous, faites-le moi savoir et je verrai ce que je peux faire. Pour utiliser la méthode, écrivez quelque chose comme:

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

PropertyCopy.Copy(instance1, instance2);

(où Copy est une méthode générique appelée en utilisant l'inférence de type).

Je ne suis pas vraiment prêt à faire une version complète de MiscUtil, mais voici le code mis à jour, y compris les commentaires. Je ne vais pas les reconditionner pour l'éditeur SO - il suffit de copier le morceau entier.

(Je repenserais probablement aussi un peu l'API en termes de nommage si je partais de zéro, mais je ne veux pas casser les utilisateurs existants ...)

#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
65
Jon Skeet

Parce que je crois que la version de Jon est un peu trop compliquée et que la version de Steve est trop simple, et j'aime l'idée de Daniel d'une classe d'extension.

De plus, une version générique est jolie mais inutile car tous les éléments sont des objets.

Je voudrais proposer ma version allégée et moyenne. Crédits à tout ce qui précède. :RÉ

Code:

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

tilisation:

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

Comme je m'ennuyais et une version linq a été suggérée par un commentaire

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

S'appuyant sur la méthode de Steve, j'ai opté pour l'approche de la méthode d'extension. Cela utilise ma classe de base comme type mais devrait être utilisable même en utilisant object comme type de param. Fonctionne très bien pour mes utilisations.

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

L'utilisation ressemble à ceci:

item1.CopyProperties(item2);

Maintenant, Item2 a les mêmes données de propriété que item1.

20
Daniel

Voici une version courte et douce, car vous avez dit que vos deux objets sont du même type:

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

Modification de la version Daniel pour éviter les exceptions.

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

Depuis plusieurs années, j'utilise une bibliothèque populaire pour celle nommée 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

Notez que même si les solutions de base sont assez simples (voir les autres réponses), il existe de nombreux cas Edge (par exemple, copie profonde, génériques, valeurs nulles) et optimisations (par exemple, cache les propriétés reflétées), il vaut donc mieux utiliser la bibliothèque maintenue pour cela.

2
Aviko

Vous pouvez utiliser la sérialisation pour cloner en profondeur l'objet:

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

Les classes devraient juste être marquées Sérialisable bien sûr.

2
Andrija

Fondamentalement, en 2019, nous devrions probablement utiliser des fonctionnalités de langage plus à jour, comme les arbres d'expression et les expressions lambda compilées, au lieu de Reflection

Comme je n'ai pas pu trouver un "cloner peu profond" qui réponde à mes exigences (vitesse surtout), j'ai décidé d'en créer un moi-même. Il énumère toutes les propriétés getting/settable, puis crée une expression Block qui est ensuite compilée et mise en cache. Ce qui le rend presque 13 fois plus rapide que le populaire AutoMapper. L'utilisation est très simple:

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

Vous pouvez voir la source complète ici: https://jitbit.github.io/PropMapper/

1
Alex

Vous pourriez essayer quelque chose comme ça ....

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
Steve

Cette méthode d'extension courte et simple vous permettra de copier les propriétés correspondantes d'un objet à un autre avec la vérification de la valeur Null et est accessible en écriture.

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);
        }
    }
1
Ashdeep Singh

Il y a ICloneable et object.MemberwiseClone (copie superficielle) (ceux-ci créent un tout nouvel objet, donc pourraient ne pas répondre à vos besoins).

Vous pouvez utiliser la réflexion pour le faire vous-même (hériter d'une classe de base afin de ne pas avoir à réimplémenter).

Ou vous pouvez coder le générer.

1
Cade Roux

Si vous voulez quelque chose comme ApplyProperties, vous pouvez écrire une méthode d'extension sur Object qui ferait ce dont vous avez besoin. Rendez-vous simplement compte qu'une telle méthode d'extension ne serait pas "pure" ou sans effets secondaires. Mais si vous avez besoin de cette capacité, c'est un moyen de l'accomplir.

0
jrista

Peut-être que ce message est un peu ancien, les réponses sont bonnes, mais lorsque vous devez mapper beaucoup d'objets dans un type cible, les propriétés de bouclage pourraient être coûteuses (imagerie de 100 accessoires et plus).

J'utilise cette méthode AutoMapFactory (ne nécessite que LinQ), elle itérera une seule fois dans les propriétés et le mappage de chaque objet sera rapide (100000 accessoires/secondes)

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

Comment utiliser ceci:

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