かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

インターフェースの実装は横断的関心事?

S2Daoとかでやられてることなので、特に目新しいことはないのですがUnityでもInterceptor使えば同じようなことができるということでお試し。ネタです。

IDataErrorInfoを実装してみよう

Interceptor使ってIDataErrorInfoを実装してみる遊び。例えば以下のようなクラスを準備。インデクサーは、インターセプターで実装をしてもらうので空っぽ実装。

public class Person : IDataErrorInfo
{
    public string Error { get; private set; }

    // Interceptorで実装してもらう
    public virtual string this[string columnName]
    {
        get { throw new NotImplementedException(); }
    }

    [Required(ErrorMessage = "名前を入力してください")]
    public string Name { get; set; }
}

UnityのインターセプターはIInterceptorBehaviorインターフェースを実装して作る。

public class DataErrorInfoImplementationBehavior : IInterceptionBehavior
{
    public IEnumerable<Type> GetRequiredInterfaces()
    {
        yield return typeof(IDataErrorInfo);
    }

    public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
    {
        // インデクサーかどうか
        if (input.MethodBase.Name != "get_Item" || input.Arguments.Count != 1)
        {
            return getNext()(input, getNext);
        }

        // インデクサーならプロパティの検証
        var ctx = new ValidationContext(input.Target, null, null)
        {
            MemberName = input.Arguments[0] as string
        };

        var results = new List<ValidationResult>();
        var success = Validator.TryValidateProperty(
            input.Target.GetType().GetProperty(ctx.MemberName).GetValue(input.Target, null),
            ctx,
            results);
        if (success)
        {
            // 検証結果がOKなら空文字を返す
            return input.CreateMethodReturn("");
        }

        // 検証エラーがあるならエラーの内容を返す
        return input.CreateMethodReturn(results.First().ErrorMessage);
    }

    public bool WillExecute
    {
        get { return true; }
    }
}

Invokeメソッドが肝です。getNext引数が癖が強いけど、こういうものだと覚えてしまえばなんとかなるかな・・・。

そしてお試し。

var container = new UnityContainer();
// Interceptorを有効化
container.AddNewExtension<Interception>();

// Personクラスは仮想メソッドをインターセプトして、DataErrorInfoImplementationBehaviorでインターセプトするぜ
container.RegisterType<Person>(
    new Interceptor<VirtualMethodInterceptor>(),
    new InterceptionBehavior<DataErrorInfoImplementationBehavior>());

// コンテナからインスタンスを取得して動くか試してみる
var p = container.Resolve<Person>();
Console.WriteLine("ErrorMessage: {0}", p["Name"]);
p.Name = "aaa";
Console.WriteLine("ErrorMessage: {0}", p["Name"]);

実行結果はうまく動いた!!

ErrorMessage: 名前を入力してください
ErrorMessage:

ということで、インターフェース実装するのがめんどくさかったら、UnityとかのDIコンテナがもってる疑似AOPみたいな機能を使って実装してもらうということも出来るということを頭の片隅に入れておいてもいいかもしれない。