96 votes

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 de A aux mêmes propriétés de B sans faire une affectation explicite pour chaque propriété.

Je veux sauvegarder un code comme celui-ci :

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

en faisant quelque chose comme

a.ApplyProperties(b);

Est-ce possible ?

0 votes

Existe-t-il une solution qui n'utilise pas Reflection ?

0 votes

@TharinduSathischandra - Non. La fonctionnalité la plus proche qui existe est la suivante MemberwiseClone Cependant, cela crée toujours un nouvel objet ; il n'y a aucun moyen d'écraser toutes les propriétés d'un objet existant. (Lisez également les avertissements concernant MemberwiseClone, avant de l'utiliser).

99voto

Azerothian Points 191

Parce que je pense que la version de Jon est un peu trop compliquée et que celle 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.

J'aimerais proposer ma version maigre et méchante. Crédits à tous ceux qui précèdent. :D

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

Utilisation :

/// <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 qu'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);
        }
    }
}

0 votes

Haha, je me disais littéralement la même chose quand j'ai fait défiler la page jusqu'à ta réponse. La solution Boucles d'or. Bien que, si je devais en écrire une, elle utiliserait certainement Linq :)

0 votes

J'obtiens une exception nulle. Je pense que cela a quelque chose à voir avec une propriété en lecture seule. Comment puis-je éviter cela ?

0 votes

Ok, j'ai résolu le problème en ajoutant juste après où : "(targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)"

78voto

Jon Skeet Points 692016

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 y copie les propriétés.

Il n'est pas nécessaire que les types soient identiques : il suffit de copier toutes les propriétés lisibles du type "source" vers le type "cible". Bien sûr, si les types sont les mêmes, cela a plus de chances de fonctionner :) Il s'agit d'une copie superficielle.

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 à l'autre, elle utilise la simple fonction PropertyInfo au moment de l'exécution - c'est plus lent que l'utilisation d'un arbre d'expression, mais l'alternative serait d'écrire une méthode dynamique, ce qui ne me plaît pas trop. Si les performances sont absolument critiques 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 à utiliser 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 ré-emballer pour l'éditeur SO - copiez simplement le morceau entier.

(Si je partais de zéro, je remanierais aussi un peu l'API en termes de dénomination, 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

0 votes

Cela échoue lors de la copie vers une cible existante si la cible n'a pas son propre constructeur, par exemple IMyType instance2 = new MyType() ; Est-ce inévitable ?

0 votes

@stovroz : Eh bien, vous devez fournir le type que vous voulez réellement instancier.

0 votes

@Jon : Mais la cible est déjà instanciée. Je pensais que vos améliorations ci-dessus étaient précisément destinées à permettre la copie vers une cible existante, mais il semble vouloir en créer une nouvelle de toute façon : Expression.MemberInit(Expression.New(typeof(TTarget)), bindings).

26voto

Daniel Points 81

En me basant sur la méthode de Steve, j'ai opté pour la méthode de la méthode d'extension. Ceci utilise ma classe de base comme type mais devrait être utilisable même en utilisant object comme types 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.

6 votes

Le code ci-dessus se cassera si l'une des propriétés est en lecture seule. Ajoutez une vérification : if (destinationPi.CanWrite) avant le SetValue pour éviter que l'exception soit levée.

0 votes

Ceci. Ça marche pour moi.

3voto

Cade Roux Points 53870

Il y a ICloneable y objet.MemberwiseClone (copie superficielle) (ces derniers créent un tout nouvel objet et peuvent donc ne pas répondre à vos besoins).

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

Ou vous pouvez le générer par code.

3voto

Andrija Cacanovic Points 3518

Vous pouvez utiliser la sérialisation pour cloner profondément 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 simplement être marquées Serializable bien sûr.

0 votes

De même, si vous souhaitez ajouter une dépendance à JSON.NET en faveur d'une dépendance à Serializable vous pouvez sérialiser et désérialiser en JSON

0 votes

Cela créera une nouvelle instance, ce qui n'est peut-être pas ce que vous souhaitez faire, si vous voulez seulement modifier la valeur de vos propriétés.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X