オブジェクトの閉鎖と完全なコピー

今日、オブジェクトの完全なコピー、つまりDeepCloneを作成するという課題に直面しました。 いくつかのコードを検討し、どのような問題が発生する可能性があり、それらを解決する方法を示します。



ソースクラス:

class ClassForClone { //here are value type fields public readonly A a; public readonly Lazy<string> lazy; protected void Func1() { //to to something; } public ClassForClone(A a) { this.a = a; lazy = new Lazy<string>(() => { // some calculations Func1(); return a.SomeText; }); } }
      
      





Object.MemberwiseClone()オブジェクトのフィールドをビット単位でコピーする機能を使用します。 フィールドをコピーするという単調な作業から私たちを救いますが、参照型を持つすべてのフィールドは自分で初期化する必要があります。



この時点で、少なくとも2つのタイプの問題があります。

  1. 参照されるフィールドaおよびlazyはreadonlyとして宣言されます 。 したがって、コンストラクタでのみ、またはフィールドを宣言するときに直接値を割り当てることができます。
  2. コンストラクターは、クラスフィールドと同じ名前のパラメーターを宣言します。これにより、コンストラクターコード自体とラムダ式コードの両方に混乱が生じます。


最初の問題は、読み取り専用フィールドをオブジェクトの同様のプロパティに置き換えることで解決できます。

 public A a { get; private set; } public Lazy<string> lazy { get; private set; }
      
      





これで、aおよびlazyは、コンストラクター内および宣言時に変更できるだけでなく、通常はクラスの任意の関数内でも変更できます。



2番目の問題をより詳細に検討します。 コンストラクターに戻りましょう。 行this.a = a;の場合 ラムダ式を使用すると、すべてがすぐにわかるわけではありません。



Func1は、クラスの現在のインスタンスのコンテキストで呼び出されます。 しかし、a.SomeText行を返すとどう解釈するのでしょうか? ほとんどの場合、著者はフィールドの値を使用することを暗示しており、実際にはパラメータthisではなくパラメータを使用しています。 そして、最も興味深いことに、フィールドaは読み取り専用として宣言されており、コンストラクターのスコープ外では変更できないため、ソースコードにエラーはありませんでした。 フィールドが読み取り専用でなくなるとすぐに、ラムダ式はコンストラクターパラメーターのSomeTextフィールド/プロパティの値を返します! そして、式のljabdaを実行することになると、フィールドaとパラメーターaはすでに互いに等しくないかもしれません。

読み取り専用フィールドを同様のプロパティに置き換えたため、ラムダ式を変更する必要があります。

 public ClassForClone(A a) { this.a = a; lazy = new Lazy<string>(() => { // some calculations Func1(); return this.a.SomeText; }); }
      
      





ただし、関数のパラメーター名がフィールド/プロパティの名前と一致しなかった場合、状況ははるかに単純です。 たとえば、次のように:

 public ClassForClone(A aParam) { a = aParam; lazy = new Lazy<string>(() => { // some calculations Func1(); return a.SomeText; }); }
      
      







次に、クローン作成機能を開始しましょう。 次のようなものを書きたいだけです。

 public object DeepClone() { var clone = (ClassForClone) MemberwiseClone(); clone.a = new A(); clone.lazy = new Lazy<string>(() => { Func1(); return a.SomeText; }); return clone; }
      
      





繰り返しますが、どのオブジェクトがクロージャで囲まれるかを忘れてはなりません。 このアプローチでは、元のオブジェクトのFunc1とa.SomeTextがクローンで呼び出されます。 したがって、正しいバージョンは次のとおりです。

 public object DeepClone() { var clone = (ClassForClone) MemberwiseClone(); clone.a = new A(); clone.lazy = new Lazy<string>(() => { clone.Func1(); return clone.a.SomeText; }); return clone; }
      
      







これから、次の結論を導き出すことができます。

  1. 関数やクラスのフィールド/プロパティに同じパラメーター名を使用しないようにするか、これを介してのみ内部フィールドへのアクセスが発生するという合意に同意してください。
  2. クロージャーの使用には注意してください。 一時オブジェクトで記憶される変数の参照または値に注意してください。
  3. クロージャーでは、可変ループ値を使用しないでください。 しかし、これはまったく別の話です。



All Articles