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
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).