studio - programacion avanzada c#
C#Uso de Reflection para copiar las propiedades de la clase base (5)
Me gustaría actualizar todas las propiedades de MyObject a otra usando 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.
El código siguiente copia sobre 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));
}
}
Estaba buscando para ver si había más atributos de BindingFlags que podría utilizar para ayudar pero fue en vano.
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:
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;
}
}
Esto no tiene en cuenta las propiedades con parámetros, ni considera los accesadores privados get / set que pueden no ser accesibles, ni considera los enumerables de solo lectura, entonces ¿aquí hay una solución extendida?
Traté de convertir a C #, pero las fuentes habituales no lo hicieron y no tengo tiempo para convertirlo.
'''''' <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
Hmm. Pensé que GetFields
te permite miembros de toda la cadena, y especificabas explícitamente BindingFlags.DeclaredOnly
si no querías miembros heredados. Así que hice una prueba rápida, y tenía razón.
Entonces noté algo:
Me gustaría actualizar todas las propiedades de MyObject a otra usando 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.
El código siguiente copia sobre 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);
Esto solo obtendrá campos (incluidos los campos privados en este tipo ), pero no propiedades . Entonces, si tienes esta jerarquía (¡disculpa 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 los BindingFlags
que especifique obtendrá f0
, f1
, f2
y _p2
, pero NO p0
o p1
(que son propiedades, no campos) O _p0
o _p1
(que son privados para las clases base y, por lo tanto, los objetos del tipo L2
no tienen esos campos.
Si desea copiar propiedades, intente hacer lo que está haciendo, pero utilizando .GetProperties
en .GetProperties
lugar.
La solución de Bogdan Litescu funciona muy bien, aunque también verificaría si puede escribir en 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);
}
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));
}
}