2009-07-29 33 views
24

Me gustaría actualizar todas las propiedades de MyObject a otra utilizando Reflection. El problema al que me refiero es que el objeto particular se hereda de una clase base y esos valores de propiedad de la clase base no se actualizan.C# Uso de Reflection para copiar las propiedades de la clase base

El siguiente código copia los valores de propiedad de nivel superior.

public void Update(MyObject o) 
{ 
    MyObject copyObject = ... 

    FieldInfo[] myObjectFields = o.GetType().GetFields(
    BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); 

    foreach (FieldInfo fi in myObjectFields) 
    { 
     fi.SetValue(copyObject, fi.GetValue(o)); 
    } 
} 

que estaba buscando para ver si había alguna más BindingFlags atributos que podría utilizar para ayudar, pero en vano.

+0

¿Qué es un ejemplo de caso de uso? – Matt

+0

Puedo pensar quizás: Envolver una API que tiene una clase que te gusta. Si devuelve ese tipo, forzará la API base completa para que se instale en cualquier proyecto que haga referencia a su contenedor. Si saca una o dos clases que le gustan, puede simplemente hacer una copia de las propiedades hacia adelante y hacia atrás, con un tiempo insignificante y una complejidad significativamente menor para el usuario de su contenedor, que ahora no requiere otras dependencias – DFTR

Respuesta

37

Prueba esto:

public void Update(MyObject o) 
    { 
     MyObject copyObject = ... 
     Type type = o.GetType(); 
     while (type != null) 
     { 
      UpdateForType(type, o, copyObject); 
      type = type.BaseType; 
     } 
    } 


    private static void UpdateForType(Type type, MyObject source, MyObject destination) 
    { 
     FieldInfo[] myObjectFields = type.GetFields(
      BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); 

     foreach (FieldInfo fi in myObjectFields) 
     { 
      fi.SetValue(destination, fi.GetValue(source)); 
     } 
    } 
+0

Esto funciona bien . Me encontré con un problema, si tengo algunas propiedades que son tipos de referencia (pocos son Icollection de ciertos tipos). Entonces, si actualizo los datos en la instancia copiada, se refleja en la instancia fuente. ¿Puedo evitar esto de alguna manera? – Novice

+0

Puedo sugerir eliminar el argumento "Tipo de tipo" en UpdateForType y agregar source.GetType() en el cuerpo. Ya conoce el tipo y el código no funcionará con tipos genéricos. – DFTR

+0

Tenga cuidado al usar métodos de extensión para el objeto. Tiene consecuencias de rendimiento y mantenimiento. Consulte el CLR a través del libro de C# sobre métodos de extensión, el autor describe cómo precisamente esto es indeseable. Sería una idea mucho mejor escribir a máquina su método de extensión o hacerlo estático-genérico. –

10

Hmm. Pensé que GetFields te saca miembros de toda la cadena y especificabas explícitamente BindingFlags.DeclaredOnly si no deseaba miembros heredados. Así que hice una prueba rápida, y tenía razón.

Entonces me di cuenta de algo:

me gustaría actualizar todas las propiedades de MiObjeto a otro usando Reflexión. El problema al que me refiero es que el objeto particular es heredado de una clase base y los clase base propiedad no están actualizados .

El siguiente código copia los valores de la propiedad del nivel superior .

public void Update(MyObject o) { 
    MyObject copyObject = ... 

    FieldInfo[] myObjectFields = o.GetType().GetFields(
    BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); 

Esto hará que sólo campos (incluyendo campos privados en este tipo), pero no propiedades. Así que si usted tiene esta jerarquía (disculpen los nombres!):

class L0 
{ 
    public int f0; 
    private int _p0; 
    public int p0 
    { 
     get { return _p0; } 
     set { _p0 = value; } 
    } 
} 

class L1 : L0 
{ 
    public int f1; 
    private int _p1; 
    public int p1 
    { 
     get { return _p1; } 
     set { _p1 = value; } 
    } 
} 

class L2 : L1 
{ 
    public int f2; 
    private int _p2; 
    public int p2 
    { 
     get { return _p2; } 
     set { _p2 = value; } 
    } 
} 

luego un .GetFields en L2 con el BindingFlags que especifique obtener f0, f1, f2 y _p2, pero NO p0 o p1 (que son propiedades, no campos) o _p0 o _p1 (que son privado a las clases de base y por tanto una objetos de tipo L2no tiene esos campos.

Si desea copiar propiedades, intente hacer lo que está haciendo, pero usando .GetProperties en su lugar.

19

Escribí esto como un método de extensión que también funciona con diferentes tipos. Mi problema es que tengo algunos modelos vinculados a formularios asp mvc y otras entidades asignadas a la base de datos. Idealmente, solo tendría 1 clase, pero la entidad está construida en etapas y los modelos asp mvc quieren validar todo el modelo a la vez.

Aquí está el código: La solución de

public static class ObjectExt 
{ 
    public static T1 CopyFrom<T1, T2>(this T1 obj, T2 otherObject) 
     where T1: class 
     where T2: class 
    { 
     PropertyInfo[] srcFields = otherObject.GetType().GetProperties(
      BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); 

     PropertyInfo[] destFields = obj.GetType().GetProperties(
      BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty); 

     foreach (var property in srcFields) { 
      var dest = destFields.FirstOrDefault(x => x.Name == property.Name); 
      if (dest != null && dest.CanWrite) 
       dest.SetValue(obj, property.GetValue(otherObject, null), null); 
     } 

     return obj; 
    } 
} 
1

Bogdan Litescu funciona muy bien, aunque también me gustaría comprobar si se puede escribir a la propiedad.

foreach (var property in srcFields) { 
     var dest = destFields.FirstOrDefault(x => x.Name == property.Name); 
     if (dest != null) 
      if (dest.CanWrite) 
       dest.SetValue(obj, property.GetValue(otherObject, null), null); 
    } 
2

Esto no toma en cuenta las propiedades con los parámetros, ni considera descriptores de acceso get/set privados que pueden no ser accesibles, ni considera enumerables de sólo lectura, así que aquí tiene una solución extendida?

He intentado convertir a C#, pero las fuentes habituales no han podido hacerlo y no tengo tiempo para convertirlo yo mismo.

''' <summary> 
''' Import the properties that match by name in the source to the target.</summary> 
''' <param name="target">Object to import the properties into.</param> 
''' <param name="source">Object to import the properties from.</param> 
''' <returns> 
''' True, if the import can without exception; otherwise, False.</returns> 
<System.Runtime.CompilerServices.Extension()> 
Public Function Import(target As Object, source As Object) As Boolean 
    Dim targetProperties As IEnumerable(Of Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo)) = 
     (From aPropertyInfo In source.GetType().GetProperties(Reflection.BindingFlags.Public Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance) 
     Let propertyAccessors = aPropertyInfo.GetAccessors(True) 
     Let propertyMethods = aPropertyInfo.PropertyType.GetMethods() 
     Let addMethod = (From aMethodInfo In propertyMethods 
          Where aMethodInfo.Name = "Add" AndAlso aMethodInfo.GetParameters().Length = 1 
          Select aMethodInfo).FirstOrDefault() 
     Where aPropertyInfo.CanRead AndAlso aPropertyInfo.GetIndexParameters().Length = 0 _ 
      AndAlso (aPropertyInfo.CanWrite OrElse addMethod IsNot Nothing) _ 
      AndAlso (From aMethodInfo In propertyAccessors 
        Where aMethodInfo.IsPrivate _ 
        OrElse (aMethodInfo.Name.StartsWith("get_") OrElse aMethodInfo.Name.StartsWith("set_"))).FirstOrDefault() IsNot Nothing 
     Select New Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo)(aPropertyInfo, addMethod)) 
    ' No properties to import into. 
    If targetProperties.Count() = 0 Then Return True 

    Dim sourceProperties As IEnumerable(Of Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo)) = 
     (From aPropertyInfo In source.GetType().GetProperties(Reflection.BindingFlags.Public Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance) 
     Let propertyAccessors = aPropertyInfo.GetAccessors(True) 
     Let propertyMethods = aPropertyInfo.PropertyType.GetMethods() 
     Let addMethod = (From aMethodInfo In propertyMethods 
          Where aMethodInfo.Name = "Add" AndAlso aMethodInfo.GetParameters().Length = 1 
          Select aMethodInfo).FirstOrDefault() 
     Where aPropertyInfo.CanRead AndAlso aPropertyInfo.GetIndexParameters().Length = 0 _ 
      AndAlso (aPropertyInfo.CanWrite OrElse addMethod IsNot Nothing) _ 
      AndAlso (From aMethodInfo In propertyAccessors 
        Where aMethodInfo.IsPrivate _ 
        OrElse (aMethodInfo.Name.StartsWith("get_") OrElse aMethodInfo.Name.StartsWith("set_"))).FirstOrDefault() IsNot Nothing 
     Select New Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo)(aPropertyInfo, addMethod)) 
    ' No properties to import. 
    If sourceProperties.Count() = 0 Then Return True 

    Try 
     Dim currentPropertyInfo As Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo) 
     Dim matchingPropertyInfo As Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo) 

     ' Copy the properties from the source to the target, that match by name. 
     For Each currentPropertyInfo In sourceProperties 
      matchingPropertyInfo = (From aPropertyInfo In targetProperties 
            Where aPropertyInfo.Item1.Name = currentPropertyInfo.Item1.Name).FirstOrDefault() 
      ' If a property matches in the target, then copy the value from the source to the target. 
      If matchingPropertyInfo IsNot Nothing Then 
       If matchingPropertyInfo.Item1.CanWrite Then 
        matchingPropertyInfo.Item1.SetValue(target, matchingPropertyInfo.Item1.GetValue(source, Nothing), Nothing) 
       ElseIf matchingPropertyInfo.Item2 IsNot Nothing Then 
        Dim isEnumerable As IEnumerable = TryCast(currentPropertyInfo.Item1.GetValue(source, Nothing), IEnumerable) 
        If isEnumerable Is Nothing Then Continue For 
        ' Invoke the Add method for each object in this property collection. 
        For Each currentObject As Object In isEnumerable 
         matchingPropertyInfo.Item2.Invoke(matchingPropertyInfo.Item1.GetValue(target, Nothing), New Object() {currentObject}) 
        Next 
       End If 
      End If 
     Next 
    Catch ex As Exception 
     Return False 
    End Try 

    Return True 
End Function 
Cuestiones relacionadas