2009-07-29 31 views
24

Tôi muốn cập nhật tất cả các thuộc tính từ MyObject sang thuộc tính khác bằng cách sử dụng Reflection. Vấn đề tôi đi vào là đối tượng cụ thể được thừa hưởng từ một lớp cơ sở và các giá trị thuộc tính lớp cơ sở đó không được cập nhật.C# Sử dụng Reflection để sao chép các thuộc tính lớp cơ sở

Bản sao mã dưới đây trên các giá trị thuộc tính cấp cao nhất.

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

Tôi đang tìm cách xem có thêm thuộc tính BindingFlags nào mà tôi có thể sử dụng để trợ giúp nhưng không có kết quả.

+0

Ví dụ về trường hợp sử dụng là gì? – Matt

+0

Tôi có thể nghĩ rằng có lẽ: Bao gồm một API có lớp bạn thích. Nếu bạn trả về kiểu đó, nó sẽ buộc toàn bộ API cơ sở phải được cài đặt trong bất kỳ dự án nào tham chiếu đến trình bao bọc của bạn. Nếu bạn rút ra một hoặc hai lớp bạn thích, bạn có thể chỉ cần sao chép các thuộc tính qua lại, với thời gian không đáng kể và độ phức tạp ít hơn đáng kể đối với người dùng trình bao bọc của bạn - hiện không yêu cầu phụ thuộc nào khác – DFTR

Trả lời

37

Hãy thử điều này:

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

. Tôi đã gặp một vấn đề, nếu có một số thuộc tính là các kiểu tham chiếu (một vài thuộc tính của Icollection). Vì vậy, nếu tôi cập nhật dữ liệu trong thể hiện được sao chép, nó sẽ phản ánh trong cá thể nguồn. Tôi có thể tránh điều này bằng một số phương tiện hay không .. – Novice

+0

Tôi có thể đề nghị xóa đối số "Loại loại" trong UpdateForType và thêm source.GetType() vào phần thân. Bạn đã biết loại và mã sẽ không hoạt động với các loại chung. – DFTR

+0

Hãy cẩn thận bằng cách sử dụng các phương pháp mở rộng cho đối tượng. Nó có hậu quả về hiệu suất và bảo trì. Xem CLR thông qua C# cuốn sách về phương pháp mở rộng, tác giả mô tả chính xác điều này là không mong muốn. Nó sẽ là một ý tưởng tốt hơn để gõ-sửa chữa phương pháp mở rộng của bạn hoặc làm cho nó tĩnh chung chung. –

10

Hmm. Tôi nghĩ rằng GetFields giúp bạn trở thành thành viên từ tất cả các cách trên chuỗi và bạn phải chỉ định rõ ràng BindingFlags.DeclaredOnly nếu bạn không muốn các thành viên được kế thừa. Vì vậy, tôi đã làm một bài kiểm tra nhanh chóng, và tôi đã đúng.

Sau đó, tôi nhận thấy một cái gì đó:

Tôi muốn cập nhật tất cả tính từ MyObject khác sử dụng Reflection. Vấn đề tôi đang đến vào là đối tượng cụ thể là được thừa kế từ một lớp cơ sở và các đối tượng lớp cơ sở thuộc tính giá trị này không được cập nhật.

Bản sao mã bên dưới trên mức cao nhất giá trị thuộc tính.

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

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

này sẽ chỉ nhận được lĩnh vực (bao gồm cả lĩnh vực tư nhân vào loại này), nhưng không tính. Vì vậy, nếu bạn có hệ thống phân cấp này (xin tha tên!):

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

sau đó một .GetFields trên L2 với BindingFlags bạn chỉ định sẽ nhận được f0, f1, f2, và _p2, nhưng KHÔNG p0 hoặc p1 (là tài sản, không lĩnh vực) hOẶC _p0 hoặc _p1 (mà là riêng tư cho các lớp cơ sở và do đó một đối tượng thuộc loại L2không có những lĩnh vực.

Nếu bạn muốn sao chép thuộc tính, hãy thử làm những gì bạn đang làm, nhưng thay vào đó hãy sử dụng .GetProperties.

19

Tôi đã viết phương thức này làm phương thức tiện ích mở rộng hoạt động với các loại khác nhau. Vấn đề của tôi là tôi có một số mô hình ràng buộc với các hình thức asp mvc, và các thực thể khác ánh xạ tới cơ sở dữ liệu. Lý tưởng nhất là tôi sẽ chỉ có 1 lớp, nhưng thực thể được xây dựng theo các giai đoạn và mô hình mvc asp muốn xác thực toàn bộ mô hình cùng một lúc.

Đây là mã: giải pháp

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 của hoạt động tuyệt vời, mặc dù tôi cũng sẽ kiểm tra xem bạn có thể viết thư cho bất động sản.

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

này không đưa vào tính chất tài khoản với các thông số, cũng không xem xét cá nhân get accessors/bộ mà có thể không thể truy cập, cũng không xem xét read-only enumerables, vì vậy đây là một giải pháp mở rộng?

Tôi đã thử chuyển đổi thành C#, nhưng các nguồn thông thường cho điều đó không thành công và tôi không có thời gian tự chuyển đổi.

''' <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 
Các vấn đề liên quan