Подтвердить что ты не робот

С# Использование Reflection для копирования свойств базового класса

Я хотел бы обновить все свойства MyObject до другого с помощью Reflection. Проблема, с которой я вхожу, заключается в том, что конкретный объект наследуется от базового класса, а значения базового класса не обновляются.

Приведенный ниже код копирует значения свойств верхнего уровня.

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

Я искал, есть ли какие-либо атрибуты BindingFlags, которые я мог бы использовать, но безрезультатно.

4b9b3361

Ответ 1

Попробуйте следующее:

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

Ответ 2

Я написал это как метод расширения, который также работает с разными типами. Моя проблема заключалась в том, что у меня есть некоторые модели, связанные с формами asp mvc и другими объектами, сопоставленными с базой данных. В идеале у меня был бы только 1 класс, но объект строился поэтапно, а модели asp mvc хотели бы сразу проверить всю модель.

Вот код:

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

Ответ 3

Хм. Я думал, что GetFields получает вас от цели до цепочки, и вам нужно явно указать BindingFlags.DeclaredOnly, если вы не хотите унаследованных членов. Поэтому я сделал быстрый тест, и я был прав.

Тогда я заметил что-то:

Я хотел бы обновить все свойстваиз MyObject в другой, используя Отражение. Проблема, с которой я иду в том, что конкретный объект унаследованные от базового класса и Значения базового класса не являются обновлено.

Нижеприведенные коды копируются поверх верхнего уровня значения свойств.

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

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

Это получит только поля (включая частные поля этого типа), но не свойства. Итак, если у вас есть эта иерархия (пожалуйста, извините имена!):

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

тогда a .GetFields на L2 с указанным вами BindingFlags будет получать f0, f1, f2 и _p2, но NOT p0 или p1 (которые свойства, а не поля) OR _p0 или _p1 (которые являются частными для базовых классов и, следовательно, объекты типа L2 не имеют этих полей.

Если вы хотите скопировать свойства, попробуйте сделать то, что вы делаете, но вместо этого используйте .GetProperties.

Ответ 4

Это не учитывает свойства с параметрами и не рассматривает ли частные получатели/блокировки доступа, которые могут быть недоступны, и не учитывает списки только для чтения, поэтому здесь расширенное решение?

Я попытался преобразовать в С#, но обычные источники для этого не сделали этого, и у меня нет времени для его преобразования.

''' <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

Ответ 5

Решение Bogdan Litescu отлично работает, хотя я бы также проверил, можете ли вы писать в собственность.

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