簡體   English   中英

C#使用反射復制基類屬性

[英]C# Using Reflection to copy base class properties

我想使用反射將所有屬性從MyObject更新為另一個。 我遇到的問題是特定對象是從基類繼承的,而這些基類的屬性值未更新。

以下代碼復制了頂級屬性值。

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屬性可以用來幫助但無濟於事。

嘗試這個:

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

我將此作為擴展方法編寫,該擴展方法也適用於不同類型。 我的問題是我有一些模型綁定到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;
    }
}

我以為GetFields讓你從一路上漲的鏈成員,你必須顯式指定BindingFlags.DeclaredOnly如果你不想繼承的成員。 所以我做了一個快速測試,我是對的。

然后我注意到了一些:

我想使用反射將所有屬性從MyObject更新為另一個。 我遇到的問題是特定對象是從基類繼承的,而這些基類的屬性值未更新。

以下代碼復制了頂級屬性值。

 public void Update(MyObject o) { MyObject copyObject = ... FieldInfo[] myObjectFields = o.GetType().GetFields( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); 

這將僅獲取字段 (包括此類型的私有字段),而不會獲取properties 因此,如果您具有此層次結構(請原諒!):

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

那么L2具有您指定的BindingFlags.GetFields將獲得f0f1f2_p2 ,但不會是p0p1 (這是屬性,不是字段)或_p0_p1 (這是基類的私有屬性,因此L2類型的對象沒有這些字段。

如果要復制屬性,請嘗試做您正在做的事情,但改用.GetProperties

這沒有考慮帶有參數的屬性,也沒有考慮可能無法訪問的私有獲取/設置訪問器,也沒有考慮只讀枚舉,因此這是擴展的解決方案嗎?

我曾嘗試將其轉換為C#,但是通常的源代碼都沒有這樣做,而且我沒有時間自己轉換它。

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

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

我有一個從基礎對象派生的對象,並為某些情況添加了額外的屬性。 但是想在派生對象的新實例上設置所有基礎對象屬性。 即使稍后向基礎對象添加更多屬性,我也不必擔心添加硬編碼行來設置派生對象中的基礎屬性。

感謝maciejkow,我提出了以下建議:

// base object
public class BaseObject
{
    public int ID { get; set; } = 0;
    public string SomeText { get; set; } = "";
    public DateTime? CreatedDateTime { get; set; } = DateTime.Now;
    public string AnotherString { get; set; } = "";
    public bool aBoolean { get; set; } = false;
    public int integerForSomething { get; set; } = 0;
}

// derived object
public class CustomObject : BaseObject
{
    public string ANewProperty { get; set; } = "";
    public bool ExtraBooleanField { get; set; } = false;

    //Set base object properties in the constructor
    public CustomObject(BaseObject source)
    {
        var properties = source.GetType().GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);

        foreach(var fi in properties)
        {
            fi.SetValue(this, fi.GetValue(source));
        }
    }
}

可以像這樣簡單地使用:

public CustomObject CreateNewCustomObject(BaseObject obj, string ANewProp, bool ExtraBool)
{
    return new CustomObject(obj)
    {
        ANewProperty = ANewProp,
        ExtraBooleanField = ExtraBool
    };
}

我的其他想法:

  • 簡單地投射對象會起作用嗎? (CustomObject)baseObject

    (我測試了強制轉換,並獲得了System.InvalidCastException: 'Unable to cast object of type 'BaseObject' to type 'CustomObject'.'

  • 序列化為JSON字符串並反序列化為CustomObject?

    (我測試了序列化/反序列化-像一個魅力一樣工作,但是序列化/反序列化存在明顯的滯后性)

因此,在我的測試案例中,使用反射在派生對象的構造函數中設置屬性是即時的。 我確信JSON Serialize / Deserialize在任何情況下也都會使用反射,但是要進行兩次,而在具有反射的構造函數中進行轉換僅發生一次。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM