this.Add() と form1.Add() の違い

RemotingServices.ExecuteMessage を使った実装は、非常に簡単ではあるものの、proxy の管理に関する問題として、this の取り扱いがある。

  public class Form1 : MarshalByRefObject, ICloneable
  {
    private int current = 0;
    public int Add(int value)
    {
      checked
      {
        int oldValue = this.current;
        this.current += value;
        return oldValue;
      }
    }

    public int Increment()
    {
      return this.Add(1);
    }

    public Form1 Clone()
    {
      return this;
    }

    private object IClonable.Clone()
    {
      return this.Clone();
    }
  }

Form1 に Increment() と Clone() を追加してみた。
このような Clone() を実装することはほとんどないだろうが、StringBuilder.Append() などのように、何らかのメソッド呼び出しの結果、this や、this を保持する別のオブジェクトを戻り値にする場合があるので、そのような例だと思ってほしい。

まずは、Increment() に関する問題だ。
proxy.Increment() の呼び出しが行われると、stub.Invoke() が呼び出され、RemotingServices.ExecuteMessage() によって Form1.Increment() が呼び出される。
Form1.Increment() の実装では、this.Add(1) が呼び出されるが、ここで利用されている this は stub.Invoke() で渡した stub.target であり、Increment() の呼び出しを受けた proxy ではない。
この結果、this.Add(1) のメソッド呼び出しは、stub.Invoke() を経由せず、直接 stub.target.Add() を呼び出してしまい、メソッド呼び出しの記録が行われない。

この問題は Clone() のように this を戻り値に含める場合により深刻になる。

  Form1 form1 = new Form1Proxy().GetTransparentProxy() as Form1;

  form1 = form1.Clone();

などとされてしまうと、form1 が保持する参照が proxy ではなく stub.form1 になってしまい、それ以後のすべてのメソッド呼び出しの記録を行うことが不可能になってしまう。

この問題の解決については、次回以降においておき、今回は上記のような問題を解決しないまま、Form1Proxy を MethodLogger へと汎用化し、完成度を高めることにする。