12

オブジェクトのディープ コピーを作成して、新しいコピーを変更し、変更をキャンセルして元のオブジェクトに戻すオプションが必要です。

ここでの問題は、オブジェクトが未知のアセンブリからであっても、任意の型になる可能性があることです。オブジェクトが不必要に [Serializable] 属性を持っているため、BinaryFormatterorを使用できません。XmlSerializer

メソッドを使用してこれを実行しようとしましたObject.MemberwiseClone()

public object DeepCopy(object obj)
{
    var memberwiseClone = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);

    var newCopy = memberwiseClone.Invoke(obj, new object[0]);

    foreach (var field in newCopy.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
    {
        if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
        {
            var fieldCopy = DeepCopy(field.GetValue(newCopy));
            field.SetValue(newCopy, fieldCopy);
        }
    }
    return newCopy;
}

これの問題は、列挙可能なもの (配列、リストなど) では機能せず、辞書では機能しないことです。

では、C# で不明なオブジェクトのディープ コピーを作成するにはどうすればよいでしょうか?

TNXたくさん!

4

9 に答える 9

14

任意のオブジェクトをディープ コピーすることは完全に不可能です。

たとえば、 aControlまたは aFileStreamまたは? をどのように処理しHttpWebResponseますか?

あなたのコードは、オブジェクトがどのように機能し、そのフィールドが何を含むべきかを知ることができません。

これをしないでください
災害のレシピです。

于 2010-09-05T17:46:25.923 に答える
4

任意のオブジェクトのディープ コピーを作成することは非常に困難です。オブジェクトに、書き込み機能を持つ開いているファイルやネットワーク接続などのリソースへのアクセスが含まれている場合はどうなるでしょうか? オブジェクトが保持する情報のタイプを知らなければ、オブジェクトのコピーを作成して、まったく同じように機能させることは困難です。リフレクションを使用してこれを行うことができるかもしれませんが、非常に困難になります。まず、コピーしたすべてのオブジェクトを保持するために何らかのリストが必要です。そうしないと、A が B を参照し、B が A を参照する場合、無限ループに陥る可能性があります。

于 2010-09-05T17:50:36.300 に答える
4

SLaksに同意します。ディープ コピーまたはフラット コピーを取得する天気を区別するためだけに、常にカスタム コピー セマンティクスが必要です。(参照とは何か、複合参照の意味に含まれる参照とは何か。)

あなたが話しているパターンはmemento パターンです。

実装方法については記事をお読みください。しかし、基本的には、カスタム コピー機能を作成することが判明しました。クラスの内部または「コピー ファクトリ」の外部のいずれかです。

于 2010-09-05T17:51:15.957 に答える
3

質問に答えるには、とをArray.CreateInstance呼び出して配列の内容を呼び出してコピーすることでGetValue配列をディープコピーできますSetValue

ただし、任意のオブジェクトに対してこれを行うべきではありません。

例えば:

  • イベントハンドラーはどうですか?
  • 一部のオブジェクトには一意のIDがあります。重複するIDを作成することになります
  • 親を参照するオブジェクトはどうですか?
于 2010-09-06T12:05:31.723 に答える
2

わかりました、私はあなたのルーチンを少し変更しました。クリーンアップする必要がありますが、必要なことが達成されるはずです。コントロールやファイルストリームに対してこれをテストしていないため、これらのインスタンスには注意が必要です。

Activator.CreateInstance の Memberwise クローンを回避しました。これにより、参照型とコピー値型の新しいインスタンスが作成されます。多次元配列でオブジェクトを使用する場合は、配列ランクを使用し、ランクごとに繰り返す必要があります。

    static object DeepCopyMine(object obj)
    {
        if (obj == null) return null;

        object newCopy;
        if (obj.GetType().IsArray)
        {
            var t = obj.GetType();
            var e = t.GetElementType();
            var r = t.GetArrayRank();
            Array a = (Array)obj;
            newCopy = Array.CreateInstance(e, a.Length);
            Array n = (Array)newCopy;
            for (int i=0; i<a.Length; i++)
            {
                n.SetValue(DeepCopyMine(a.GetValue(i)), i);
            }
            return newCopy;
        }
        else
        {
            newCopy = Activator.CreateInstance(obj.GetType(), true);
        }

        foreach (var field in newCopy.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
        {
            if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
            {
                var fieldCopy = DeepCopyMine(field.GetValue(obj));
                field.SetValue(newCopy, fieldCopy);
            }
            else
            {
                field.SetValue(newCopy, field.GetValue(obj));
            }
        }
        return newCopy;
    }
于 2010-09-06T20:08:48.940 に答える
2

他の人が言ったように、任意のオブジェクトをディープコピーすると悲惨な結果になる可能性があります。ただし、複製しようとしているオブジェクトがはっきりしている場合は、複製を試みることができます。

元の方法に関する2つのこと:

  • 循環参照の場合、無限ループに陥ります。
  • 心配する必要がある唯一の特別なケースは、配列であるメンバーをコピーすることです。他のすべて (リスト、ハッシュセット、辞書など) は、最終的にはそれに要約されます (または、ツリーとリンクされたリストの場合は、標準的な方法でディープ コピー可能になります)。

また、コンストラクターを呼び出さずに任意のクラス オブジェクトを作成できるメソッドがあったことにも注意してください。BinaryFormatter はそれを使用し、公開されていました。残念ながら、それが何と呼ばれ、どこに住んでいたかは覚えていません。ランタイムまたはマーシャリングに関する何か。

更新 : System.Runtime.Serialization.FormatterServices.GetUninitializedObject

GetUninitializedObject メソッドを使用して、ContextBoundObject クラスから派生した型のインスタンスを作成することはできません。

于 2010-09-05T17:58:39.030 に答える
1

任意のオブジェクトをコピーしてはいけないもう 1 つの理由: オブジェクトの背後にあるコードを調べることなく、そのオブジェクトがシステム内の他のオブジェクトとどのように関連しているか、または特定の値が魔法であるかどうかを知ることは不可能です。たとえば、2 つのオブジェクトが両方とも、5 に等しい単一の整数を保持する配列への参照を保持している場合があります。これらの配列は両方とも、プログラムの他の場所で使用されています。どちらかの配列がたまたま値 5 を保持しているということではなく、配列への書き込みが他のプログラムの実行に与える影響が重要なのです。作成者が配列の用途を知らないシリアライザーがその関係を維持できる方法はありません。

于 2010-09-05T19:55:29.473 に答える
0

@schoetbi による回答の詳細を次に示します。クラスに自分自身を複製する方法を伝える必要があります。C# は集約と構成を区別せず、両方にオブ​​ジェクト参照を使用します。

車に関する情報を格納するためのクラスがある場合、エンジン、ホイールなど (構成) のようなインスタンス フィールドだけでなく、製造元 (集約) も持つことができます。どちらも参照として保存されます。

車のクローンを作成した場合、エンジンとホイールのクローンも作成されることを期待しますが、製造元のクローンも作成されることは望ましくありません。

于 2014-09-29T12:46:10.047 に答える