web-dev-qa-db-ja.com

C#リフレクションを使用して基本クラスのプロパティをコピーする

Reflectionを使用して、すべてのプロパティを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属性が他にあるかどうかを確認しようとしていました。

25
David

これを試して:

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

これは、さまざまなタイプでも機能する拡張メソッドとして作成しました。私の問題は、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;
    }
}
20
Bogdan Litescu

うーん。 GetFieldsを使用すると、メンバーをチェーンの最上位に移動できます。継承されたメンバーが望まない場合は、明示的にBindingFlags.DeclaredOnlyを指定する必要がありました。 。だから私は簡単なテストをしました、そして私は正しかったです。

それから私は何かに気づきました:

Reflectionを使用して、すべてのpropertiesをMyObjectから別のオブジェクトに更新したいと思います。私が直面している問題は、特定のオブジェクトが基本クラスから継承され、それらの基本クラスpropertyの値が更新されないことです。

以下のコードは、最上位のプロパティ値をコピーします。

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

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

これはfields(このタイプのプライベートフィールドを含む)のみを取得しますが、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; }
    }
}

次に、指定したBindingFlagsを使用した.GetFieldsL2は、f0f1f2、および_p2を取得します。ただし、p0またはp1(フィールドではなくプロパティ)ではないOR _p0または_p1(これらは基本クラスにプライベートです)したがって、タイプL2のオブジェクトには、これらのフィールドがありません

プロパティをコピーする場合は、今行っていることを試してください。代わりに.GetPropertiesを使用してください。

10
AakashM

これは、パラメーターを持つプロパティを考慮に入れておらず、アクセスできない可能性のあるプライベートの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
2
PhilAI

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

基本オブジェクトから派生したオブジェクトがあり、特定のシナリオに追加のプロパティを追加しています。しかし、派生オブジェクトの新しいインスタンスにすべての基本オブジェクトプロパティを設定したいと考えています。後でベースオブジェクトにプロパティを追加するときでも、派生オブジェクトにベースプロパティを設定するためにハードコードされた行を追加することについて心配する必要はありません。

おかげで 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もリフレクションを使用していると思いますが、リフレクションを使用したコンストラクターでの変換は1回だけ行われるのに対し、2回実行します。

1
Pierre