1

任意のオブジェクトをディープ コピーする気の利いたメソッドを作成しました。これはMemberwiseClone()、インスタンス内の参照型フィールドを再帰的に呼び出すことによって行われます。このメソッドは、階層関係オブジェクトを含め、私が使用したいすべてのオブジェクトで完全に機能します。このメソッドは、過去の訪問の辞書も備えているため、不要な重複作業が回避されます。

ただし、私が抱えている問題は、クローンが必要なときにオブジェクトが WPF/MVVM にデータ バインドされていない場合にのみ、この方法が機能することです。INotifyPropertyChanged.PropertyChangedデータがバインドされ、メソッドが呼び出されると、イベントと WPF フレームワークの間に確立されたリンクが原因で (私は推測します)、スタック オーバーフロー例外が発生します。次に、再帰呼び出しは、AppDomainおよび低レベルの Pointer オブジェクトを含むオブジェクトのユニバース全体をコピーしようとします。これらのオブジェクトは、リンクされているように見え、ほぼ無限に続きます (とにかく、VS2012 が処理できる範囲を超えています)。

オブジェクトグラフの先頭に戻るディープコピーが必要になることはないと思いますAppDomain...特定の境界に達したときにコピーメソッドを「停止」させるスマートな方法はありますか? また、オブジェクトをデータにバインドする前に単純にコピーすることも考えましたが、それが実行可能なオプションかどうかはわかりませんし、かなりばかげています。シリアル化できないタイプで動作するシンプルなディープ コピー ソリューションが必要なだけですが、INotifyPropertyChanged.

メソッドの実装:

private static object Clone(object instance, IDictionary<object, object> visitGraph)
{
    var instanceType = instance.GetType();
    Debug.WriteLine(instanceType.Name);
    object clonedInstance = null;

    if (visitGraph.ContainsKey(instance))
    {
        clonedInstance = visitGraph[instance];
    }
    else
    {
        const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | 
                                   BindingFlags.Instance;
        var memberwiseCloneMethod =
            instanceType.GetMethods(flags).Single(x => x.Name == "MemberwiseClone" && 
            !x.GetParameters().Any());

        clonedInstance = memberwiseCloneMethod.Invoke(instance, null);

        visitGraph.Add(instance, clonedInstance);

        var allReferenceTypeProperties = clonedInstance.GetType().GetAllFields()
                                           .Where(
                                              x =>
                                              !x.FieldType.IsValueType
                                               && x.FieldType != typeof (string));

        foreach (var field in allReferenceTypeProperties)
        {
            var existingFieldValue = field.GetValue(instance);

            if (existingFieldValue != null)
            {
                var clonedFieldValue = Clone(existingFieldValue, visitGraph);
                field.SetValue(clonedInstance, clonedFieldValue);
            }
        }    
    }

    return clonedInstance;
}

public static IEnumerable<FieldInfo> GetAllFields(this Type type)
{
    const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | 
                               BindingFlags.Instance;

    var fields = type.GetFields(flags);

    foreach (var field in fields)
    {
        yield return field;
    }

    if (type.BaseType != null)
    {
        foreach (var field in GetAllFields(type.BaseType))
        {
            yield return field;
        }
    }
}

public static object Copy(this object instance)
{
    if (instance == null) throw new ArgumentNullException("instance");
    var visitGraph = new Dictionary<object, object>();
    var clonedInstance = Clone(instance, visitGraph);
    return clonedInstance;
}
4

1 に答える 1