かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

インターフェースの実装は横断的関心事?(その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にインスタンス生成を任せないといけないので、そういう縛りは出てきちゃうのでちょっと嫌な感じもありますね。

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