C# 使用反射复制基类属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1198886/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 10:40:29  来源:igfitidea点击:

C# Using Reflection to copy base class properties

c#reflectioncopy

提问by David

I would like to update all properties from MyObject to another using Reflection. The problem I am coming into is that the particular object is inherited from a base class and those base class property values are not updated.

我想使用反射将 MyObject 中的所有属性更新为另一个属性。我遇到的问题是特定对象是从基类继承的,并且这些基类属性值没有更新。

The below code copies over top level property values.

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

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

I was looking to see if there were any more BindingFlags attributes I could use to help but to no avail.

我想看看是否还有更多 BindingFlags 属性可以用来帮助但无济于事。

采纳答案by maciejkow

Try this:

尝试这个:

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

回答by AakashM

Hmm. I thought GetFieldsgets you members from all the way up the chain, and you had to explicitly specifiy BindingFlags.DeclaredOnlyif you didn'twant inherited members. So I did a quick test, and I was right.

唔。我认为GetFields让你从整个链条中获得成员,BindingFlags.DeclaredOnly如果你想要继承成员,你必须明确指定。所以我做了一个快速测试,我是对的。

Then I noticed something:

然后我注意到了一些事情:

I would like to update all propertiesfrom MyObject to another using Reflection. The problem I am coming into is that the particular object is inherited from a base class and those base class propertyvalues are not updated.

The below code copies over top level property values.

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

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

我想 使用反射将 MyObject 中的所有属性更新为另一个属性。我遇到的问题是特定对象是从基类继承的,并且这些基类属性值没有更新。

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

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

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

This will get only fields(including private fields on this type), but not properties. So if you have this hierarchy (please excuse the names!):

这将只获得字段(包括此类型的私有字段),而不是属性。所以如果你有这个层次结构(请原谅名字!):

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

then a .GetFieldson L2with the BindingFlagsyou specify will get f0, f1, f2, and _p2, but NOT p0or p1(which are properties, not fields) OR _p0or _p1(which are private to the base classes and hence an objects of type L2does not havethose fields.

那么.GetFieldsL2BindingFlags你指定会得到f0f1f2,和_p2,而不是p0p1(这是性能,而不是字段)OR_p0_p1(这是私人的基类,因此类型的对象L2不具有这些领域。

If you want to copy properties, try doing what you're doing, but using .GetPropertiesinstead.

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

回答by Bogdan Litescu

I wrote this as an extension method that works with different types too. My issue was that I have some models bound to asp mvc forms, and other entities mapped to the database. Ideally I would only have 1 class, but the entity is built in stages and asp mvc models want to validate the entire model at once.

我将其编写为适用于不同类型的扩展方法。我的问题是我有一些模型绑定到 asp mvc 表单,而其他实体映射到数据库。理想情况下,我只有 1 个类,但实体是分阶段构建的,asp mvc 模型希望一次验证整个模型。

Here is the code:

这是代码:

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

回答by user885959

Bogdan Litescu's solution works great, although I would also check if you can write to property.

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

回答by PhilAI

This doesn't take into account properties with parameters, nor does it consider Private get/set accessors which may not be accessible, nor does it consider read-only enumerables, so here's an extended solution?

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

I tried converting to C#, but the usual sources for that failed to do so and I don't have the time to convert it myself.

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

回答by Pierre

I have an object which I derive from a base object, and add extra properties for certain scenarios. But would like to set all base object properties on a new instance of the derived object. Even when adding more properties to the base object later on, I don't have to worry about adding hard coded lines to set the base properties in the derived object.

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

Thanks to maciejkowI came up with the following:

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

Can be simply used like:

可以简单地使用,如:

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

Other thoughts I had:

我的其他想法:

  • Will simply casting the object work? (CustomObject)baseObject

    (I tested casting and got System.InvalidCastException: 'Unable to cast object of type 'BaseObject' to type 'CustomObject'.')

  • Serialize to JSON string and Deserialize to CustomObject?

    (I tested Serialize/Deserialize - Worked like a charm, but there is a noticeable lag in serializing/deserializing)

  • 简单地投射对象会起作用吗? (CustomObject)baseObject

    (我测试了铸造并得到了System.InvalidCastException: 'Unable to cast object of type 'BaseObject' to type 'CustomObject'.'

  • 序列化为 JSON 字符串并反序列化为 CustomObject?

    (我测试了序列化/反序列化 - 像魅力一样工作,但序列化/反序列化有明显的滞后)

So setting the properties with reflection in the constructor of the derived object is instant in my test case. I am sure JSON Serialize/Deserialize also uses reflection in anycase, but doing it twice whereas converting it in the constructor with reflection only happens the once.

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