Unity提供了自带的拦截器,如果你并不想编写繁琐的拦截器可以选择编写更轻量的方式Behavior。拦截器的应用场景有不少,比如一些数据访问框架,它们的核心概念是AOP。通过创建一个继承于类型的代理类型,并重写它的virtual函数将拦截器置入其中。前置处理函数负责打开数据库连接、启动事务,后置处理器负责提交事务、关闭数据库连接。Unity的VirtualMethodInterceptor就可以帮助我们完成这个功能。
如果你熟悉WPF/Silverlight,那么一定了解这两年流行的MVVM。WPF/Silverlight的数据绑定基于DataContext类型是一个DependencyObject还是一个实现了INotifyPropertyChanged接口的类型。当绑定类型的属性发生改变,数据绑定机制会主动将变化更新到UI。由于所有ViewModel都需要实现INotifyPropertyChanged接口,并且需要在每个属性的setter上显示的产生属性变化的通知:
1 public class MainViewModel : INotifyPropertyChanged 2 { 3 private String m_name; 4 5 public MainViewModel() { } 6 7 public virtual String Name 8 { 9 get { return m_name; } 10 set 11 { 12 if (!String.Equals(m_name, value)) 13 { 14 m_name = value; 15 16 OnPropertyChanged(“Name”); 17 } 18 } 19 } 20 21 private void OnPropertyChanged(String propertyName) 22 { 23 PropertyChangedEventHandler handler = PropertyChanged; 24 25 if (handler != null) 26 handler(this, new PropertyChangedEventArgs(propertyName)); 27 } 28 29 #region INotifyPropertyChanged Members 30 31 public event PropertyChangedEventHandler PropertyChanged; 32 33 #endregion 34 }
上面的视图模型可以优化,创建一个ViewModelBase的基类,实现了INotifyPropertyChanged接口,并提供了OnPropertyChanged函数。但对于ViewModel的开发人员来说仍旧需要在各处添加触发PropertyChanged事件的函数或代码。我们可以通过Unity的VirtualMethodInterceptor,编写一个NotifyPropertyChangedBehavior实现属性变化的通知。看一个示例:
1 // <summary> 2 /// 属性变化行为 3 /// </summary> 4 public sealed class NotifyPropertyChangedBehavior : IInterceptionBehavior 5 { 6 /// <summary> 7 /// 添加事件函数信息 8 /// </summary> 9 private static readonly MethodInfo AddEventMethodInfo = typeof(INotifyPropertyChanged).GetEvent(“PropertyChanged”).GetAddMethod(); 11 12 /// <summary> 13 /// 删除事件函数信息 14 /// </summary> 15 private static readonly MethodInfo RemoveEventMethodInfo = typeof(INotifyPropertyChanged).GetEvent(“PropertyChanged”).GetRemoveMethod(); 17 18 /// <summary> 19 /// 属性变化事件 20 /// </summary> 21 private event PropertyChangedEventHandler PropertyChanged; 22 23 /// <summary> 24 /// 构造函数 25 /// </summary> 26 public NotifyPropertyChangedBehavior() { } 27 28 /// <summary> 29 /// 是否为属性Setter 30 /// </summary> 31 /// <param name=”input”>输入</param> 32 /// <returns>为属性Setter</returns> 33 private static Boolean IsPropertySetter(IMethodInvocation input) 34 { 35 return input.MethodBase.IsSpecialName && input.MethodBase.Name.StartsWith(“set_”); 36 } 37 38 /// <summary> 39 /// 添加事件订阅 40 /// </summary> 41 /// <param name=”input”>输入</param> 42 /// <param name=”getNext”>下一个行为</param> 43 /// <returns>函数返回值</returns> 44 private IMethodReturn AddEventSubscription(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) 45 { 46 var subscriber = (PropertyChangedEventHandler)input.Arguments[0]; 47 48 this.PropertyChanged += subscriber; 49 50 return input.CreateMethodReturn(null); 51 } 52 53 /// <summary> 54 /// 删除事件订阅 55 /// </summary> 56 /// <param name=”input”>输入</param> 57 /// <param name=”getNext”>下一个行为</param> 58 /// <returns>函数返回值</returns> 59 private IMethodReturn RemoveEventSubscription(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) 60 { 61 var subscriber = (PropertyChangedEventHandler)input.Arguments[0]; 62 63 this.PropertyChanged -= subscriber; 64 65 return input.CreateMethodReturn(null); 66 } 67 68 /// <summary> 69 /// 拦截属性设置 70 /// </summary> 71 /// <param name=”input”>输入</param> 72 /// <param name=”getNext”>下一个行为</param> 73 /// <returns>函数返回值</returns> 74 private IMethodReturn InterceptPropertySet(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) 75 { 76 var propertyName = input.MethodBase.Name.Substring(4); 77 var returnValue = getNext()(input, getNext); 78 var subscribers = PropertyChanged; 79 80 if (subscribers != null) 81 subscribers(input.Target, new PropertyChangedEventArgs(propertyName)); 82 83 return returnValue; 84 } 85 86 #region IInterceptionBehavior Members 87 88 /// <summary> 89 /// 是否将执行 90 /// </summary> 91 public Boolean WillExecute 92 { 93 get { return true; } 94 } 95 96 /// <summary> 97 /// 获得需要的接口遍历器 98 /// </summary> 99 /// <returns>接口遍历器</returns> 100 public IEnumerable<Type> GetRequiredInterfaces() 101 { 102 return new[] { typeof(INotifyPropertyChanged) }; 103 } 104 105 /// <summary> 106 /// 调用 107 /// </summary> 108 /// <param name=”input”>输入</param> 109 /// <param name=”getNext”>下一个行为</param> 110 /// <returns>函数返回值</returns> 111 public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) 112 { 113 // 如果为添加事件 114 if (input.MethodBase == AddEventMethodInfo) 115 return AddEventSubscription(input, getNext); 116 117 // 如果为删除事件 118 if (input.MethodBase == RemoveEventMethodInfo) 119 return RemoveEventSubscription(input, getNext); 120 121 // 设置属性 122 if (IsPropertySetter(input)) 123 return InterceptPropertySet(input, getNext); 124 125 return getNext()(input, getNext); 126 } 127 128 #endregion 129 } 130 131 public class MainViewModel 132 { 133 public MainViewModel() { } 134 135 public virtual String Name { get; set; } 136 } 137 138 IUnityContainer unityContainer = new UnityContainer(); 139 140 unityContainer.AddNewExtension<Interception>(); 141 unityContainer.RegisterType<MainViewModel>(new Interceptor<VirtualMethodInterceptor>(), new InterceptionBehavior(new NotifyPropertyChangedBehavior())); 142 143 MainViewModel viewModel = unityContainer.Resolve<MainViewModel>(); 144 145 ((INotifyPropertyChanged)viewModel).PropertyChanged += new PropertyChangedEventHandler((sender, e) => Console.WriteLine(e.PropertyName)); 146 147 viewModel.Name = “hello, world”;
上面的示例可以看到MainViewModel被附加了PropertyChanged行为,开发人员不再需要为视图模型的属性变化编写大量重复的代码。一个拦截行为需要实现IInterceptionBehavior的三个定义:WillExecute属性、GetRequiredInterfaces函数、Invoke函数。
WillExecute:定义当前行为是否将被执行,开发人员可以根据不同情况编写相应逻辑。
GetRequiredInterfaces:返回需要被附加的接口,比如MainViewModel并没有实现INotifyPropertyChanged,但是被拦截后创建的代理类型被附加了INotifyPropertyChanged接口。
Invoke:拦截后的真正调用,getNext是下一个行为,整个行为附加是一个链式调用。