【问题标题】:C# Using Reflection to copy base class propertiesC# 使用反射复制基类属性
【发布时间】:2010-11-14 23:52:06
【问题描述】:

我想使用反射将所有属性从 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 属性可以用来提供帮助,但无济于事。

【问题讨论】:

  • 什么是用例示例?
  • 我可能会想:包装一个有你喜欢的类的 API。如果您返回该类型,它将强制将整个基本 API 安装在任何引用您的包装器的项目中。如果你取出你喜欢的一两个类,你可以简单地来回复制属性,时间可以忽略不计,并且对于包装器的用户来说复杂性显着降低 - 现在不需要其他依赖项

标签: c# reflection copy


【解决方案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));
    }
}

【讨论】:

  • 这很好用。我遇到了一个问题,如果有一些属性是引用类型(很少是某些类型的 Icollection)。因此,如果我更新复制实例中的数据,它会反映在源实例中。我可以通过某种方式避免这种情况吗..
  • 我可以建议删除 UpdateForType 中的“类型类型”参数,并在正文中添加 source.GetType()。您已经知道类型,代码不适用于泛型类型。
  • 谨慎使用对象的扩展方法。它具有性能和可维护性的后果。请参阅 CLR via C# book on extension methods,作者描述了这是多么不受欢迎。对扩展方法进行类型修复或使其成为静态泛型会更好。
  • 您可以使用 BindingFlags.FlattenHierarchy,这包括 FieldInfo[] 数组中继承类的所有字段,私有静态除外。
  • 我刚才测试了BindingFlags.FlattenHierarchy。它工作正常。
【解决方案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 到另一个使用 反射。我来的问题 into 是特定对象是 继承自基类和那些 基类 property 值不是 更新了。

    下面的代码复制到顶层 属性值。

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

    然后在 L2 上的 .GetFields 和您指定的 BindingFlags 将得到 f0f1f2_p2,但不是 p0p1(它们是属性,而不是字段)或 _p0_p1(它们是基类私有的,因此 L2 类型的对象没有这些字段。

    如果您想复制属性,请尝试执行您正在执行的操作,但改用 .GetProperties

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        这不考虑带参数的属性,也不考虑可能无法访问的 Private get/set 访问器,也不考虑只读枚举,所以这是一个扩展的解决方案?

        我尝试转换为 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
        

        【讨论】:

          【解决方案6】:

          我有一个从基础对象派生的对象,并为某些场景添加了额外的属性。但想在派生对象的新实例上设置所有基础对象属性。即使稍后向基础对象添加更多属性,我也不必担心添加硬编码行来设置派生对象中的基础属性。

          感谢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?

            (我测试了 Serialize/Deserialize - 效果很好,但是在序列化/反序列化方面有明显的滞后)

          因此,在我的测试用例中,在派生对象的构造函数中使用反射设置属性是即时的。我确信 JSON Serialize/Deserialize 在任何情况下也使用反射,但是会执行两次,而在构造函数中使用反射进行转换只会发生一次。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-05-10
            • 1970-01-01
            • 1970-01-01
            • 2014-10-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-04-04
            相关资源
            最近更新 更多