かずきのBlog@hatena

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

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

前回と同じのりでINotifyPropertyChangedも実装できますよっと。IDataErrorInfoの例は、基本クラス用意して継承したほうが断然いいと思いますがINotifyPropertyChangedを実装してプロパティにいちいちイベント発行コードかくのだるいので、こっちのほうがありがたみあるかもしれません。
ということでコード

using System;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.EnterpriseLibrary.Validation;
using System.ComponentModel;
using Microsoft.Practices.Unity.InterceptionExtension;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Practices.Unity;

namespace ConsoleApplication4
{
    public class Person : INotifyPropertyChanged
    {
        public virtual event PropertyChangedEventHandler PropertyChanged;
        protected virtual void RaisePropertyChanged(string name)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
        public virtual string Name { get; set; }

        private int age;
        public int Age
        {
            get { return age; }
            set
            {
                this.age = value;
                this.RaisePropertyChanged("Age");
                this.RaisePropertyChanged("Name");
            }
        }
    }

    public class NotifyPropertyChangedBehavior : IInterceptionBehavior
    {
        private static readonly EventInfo PropertyChangedEventInfo = typeof(INotifyPropertyChanged).GetEvent("PropertyChanged");
        private event PropertyChangedEventHandler handler;

        public IEnumerable<Type> GetRequiredInterfaces()
        {
            yield return typeof(INotifyPropertyChanged);
        }

        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            if (input.MethodBase.Name == PropertyChangedEventInfo.GetAddMethod().Name)
            {
                var d = input.Arguments[0] as PropertyChangedEventHandler;
                this.handler += d;
                return getNext()(input, getNext);
            }

            if (input.MethodBase.Name == PropertyChangedEventInfo.GetRemoveMethod().Name)
            {
                var d = input.Arguments[0] as PropertyChangedEventHandler;
                this.handler -= d;
                return getNext()(input, getNext);
            }

            if (input.MethodBase.IsSpecialName && input.MethodBase.Name.StartsWith("set_"))
            {
                var result = getNext()(input, getNext);
                var name = input.MethodBase.Name.Substring(4);
                if (this.handler != null)
                {
                    this.handler(input.Target, new PropertyChangedEventArgs(name));
                }
                return result;
            }

            return getNext()(input, getNext);
        }

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


    class Program
    {
        static void Main(string[] args)
        {
            var c = new UnityContainer();
            c.AddNewExtension<Interception>();

            c.RegisterType<Person>(
                new Interceptor<VirtualMethodInterceptor>(),
                new InterceptionBehavior<NotifyPropertyChangedBehavior>());

            var p = c.Resolve<Person>();
            p.PropertyChanged += (_, e) => Console.WriteLine(e.PropertyName);
            p.Name = "a";
            p.Name = "bbb";
            p.Age = 10;
        }
    }
}

virtualをつけたプロパティのsetを横取りしてます。virtualをつけなければ自分で全部実装すればOKというノリです。まぁこういうことやるとUnityにインスタンス生成を任せないといけないので、そういう縛りは出てきちゃうのでちょっと嫌な感じもありますね。

ということで、こういうことをやるときは、きちんと破綻しないことを確信してからやるのがいいと思います。以上マル。