繁体   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