IContextAttribute の実装

ContextAttribute クラスは IContextAttribute だけでなく、IContextProperty の面倒もみてくれていますが、おそらく、一般的な実装を提供してくれているので、そのまま ContextAttribute を利用してみます。
ContextAttribute クラスのコンストラクタは名前を要求してくるので、"MethodLogger" を指定して呼び出してみましょう。

[AttributeUsage(AttributeTarget.Class)]
public class MethodLoggerAttribute : ContextAttribute
{
  public MethodLoggerAttribute() : base("MethodLogger") {}
}

[MethodLogger]
public class Test : ContextBoundObject
{
  public Test() {}
  public static Test CreateNew() { return new Test(); }
}

記述していませんが、ContextAttribute の仮想メソッドはすべて override して、呼び出しと結果を記録するだけの実装を追加しています。*1
この MethodLoggerAttribute を付与したオブジェクトを生成することで、ContextAttribute の役割が大体わかってきます。
まず、IContextAttribute.IsContextOK() が呼び出され、新規オブジェクトと現在のコンテキストが合致しているかどうかをチェックされます。このチェックに成功すると、以降の動作はすべてパスされます。
失敗した場合は、まず IContextAttribute.GetPropertiesForNewContext() が呼び出され、続けて、IContextProperty.Freeze() が呼び出された後、IContextProperty.IsNewContextOK() が呼び出されます。
動きだけでは予測し辛い面も多いため SDK 付属の IL Disassembler も活用し、ContextAttribute クラスの実装を簡単にチェックしてみると、GetPropertiesForNewContext() では、Context にプロパティを追加登録しており、IsContextOK() はこのプロパティがすでに登録されているかどうかをチェックしているようです。

IContextProperty の実装

IContextProperty 関係のメソッド呼び出しは、コールスタックを少し遡って見ると、ActivationServices.DoCrossContextActivation() などにたどりつくことができますが、用途も機能もわかりそうでわからないイメージですし、ContextAttribute クラスの実装がほとんど空なので、当面は深く考えないことにします。

AttributeUsage.AllowMultiple

MethodLoggerAttribute と SynchronizationAttribute を同時に設定すると、双方が共に初期化されますが、MethodLoggerAttribute を2つ設定しても、片一方にしかメソッド呼び出しが発生しません。
これは、GetPropertiesForNewContext() で設定するプロパティによらないようなので、引数によって複数の挙動を切り替えられるような属性を、機能として2つ以上コンテキストに設定することはできないということに注意しなければならないでしょう。
そのような用途には、属性を複数個設定するのえではなく、設定するプロパティの内容をみて挙動を切り替えられるようにすればよい、ということでしょうか。

*1:ContextAttribute は IContextAttribute と IContextProperty のすべてのメソッドを仮想メソッドとして実装している

IMessageSink の実装(メソッドのロギング)

MethodLogger クラスの実装によるものとほとんど同じですが、メッセージシンクは実装を提供しなくてよいので、非常に簡潔になります。

public class MethodLoggerSink : IMessageSink
{
  private readonly IMessageSink next;

  public MethodLoggerSink(IMessageSink next)
  {
    this.next = next;
  }

  public IMessageSink NextSink
  {
    get { return this.next; }
  }

  public IMessage SyncProcessMessage(IMessage msg)
  {
    LogCallMsg(msg as IMethodCallMessage);
    
    // 次へ転送する
    return this.NextSink.SyncProcessMessage(msg);
  }
  
  public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
  {
    LogCallMsg(msg as IMethodCallMessage);
    return this.NextSink.AsyncProcessMessage(msg, replySink);
  }

  // メソッド呼び出しを記録する
  private void LogCallMsg(IMethodCallMessage req)
  {
    if (req != null)
    {
      Debug.WriteLine(req.MethodName + "が呼び出されました。");
    }
  }
}

とりあえず、IDynamicMessageSink は考えないことにしています。

IMessageSink の提供

さて、実際に上記のクラスを提供する部分ですが、ServerContextSink, ServerContextSink, EnvoySink, ObjectSink の4つのエントリから適切なものを選択しなければなりません。
このうち、簡単に予測がつくものは ServerContext と ClientContext で、これらは MethodLogger の実装で利用してきたリモート処理の端点である server object と proxy object におけるメッセージ交換に相当するであろうことは容易に想像がつきます。
とりあえず、4つのポイントすべてに対して MethodLoggerSink をつっこんでみると、たっぷり呼び出しのログが手に入るので、順番にみていこう。

誰が IContributeXXX を実装するのか

それに先立って、見ておくべきことの1つに、IContributeXXX がどこから取得されるのか? という問題がある。
ContextAttribute の派生クラスとして実装を進めていると、ContextAttribute が IContributeXXX を実装していれば、ContextBoundObject の生成時に MessageSink を登録することができるのだが、Attribute 派生クラスとして IContextAttribute を実装している場合には、IContributeXXX の実装を行っても MessageSink を提供することができない。
IContributeXXX を実装するオブジェクトは、IContextAttribute.GetPropertiesForNewContext() において Context.Properties に対して追加される IContextProperty の実装クラスである。

よって、IContextAttribute と IContextProperty を個別に実装する場合は、

public class MethodLoggerAttribute : Attribute, IContextAttribute
{
  // (略)
}

public class MethodLoggerProperty : IContextProperty,
  IContributeServerContextSink, IContributeClientContextSink, ...
{
  // (略)
}

というような具合になる。覚えておいて損はないかもしれない。