AOP 实现 INotifyPropertyChanged
对于懒人来说,如何实现 INotifyPropertyChanged 接口:)
引言
本文档描述了如何使用 Postsharp 通过 AOP 实现 INotifyPropertyChanged
接口。
背景
尝试实现 INotifyPropertyChanged
接口时,您很可能会在短时间内感到沮丧,仅仅因为您需要对每个类执行相同的步骤。
可能的解决方案(从差到好)
复制 & 粘贴
好的,复制 & 粘贴在这里可以工作,但这会产生非常高的冗余。
示例
我们将在这个解决方案中查看这个例子。
/// <summary>
/// A Person-Class
/// </summary>
public class Person : INotifyPropertyChanged
{
private string firstName;
private string lastName;
/// <summary >
/// Firstname of the Person
/// </summary >
public string FirstName
{
get { return firstName; }
set
{
firstName = value;
FirePropertyChanged("FirstName");
FirePropertyChanged("Name");
}
}
/// <summary >
/// Lastname of the Person
/// </summary >
public string LastName
{
get { return lastName; }
set
{
lastName = value; FirePropertyChanged("LastName");
FirePropertyChanged("Name");
}
}
/// <summary >
/// Complete Name
/// </summary >
public string Name
{
get
{
return FirstName + " " + LastName;
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void FirePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
在这里,你可以看到实现 INotifyPropertyChanged
接口的标准方法。
消除冗余
正如你所看到的,第一个解决方案会导致高冗余。现在,我们意识到相似之处并实现一个抽象类。
示例
在这里,我们在抽象类中实现了 INotifyPropertyChanged
成员和我们的辅助函数 'FirePropertyChanged
',并将我们的 Person
类从这个基类 'NotifyingObject
' 继承而来。
public abstract class NotifyingObject : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void FirePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this,
new PropertyChangedEventArgs(propName));
}
}
#endregion
}
/// <summary >
/// A Person-Class
/// </summary >
public class Person : NotifyingObject
{ /*...*/ }
现在,你可能会说“就是这样了!”。是的,当然。这是一种很好的实现接口的方法。但在 C# 中,没有多重继承。
public class xyz{}
public class Person : NotifyingObject, xyz {}
上面的代码将无法编译!
AOP 的方法
在此步骤中,我们将我们的基类 'NotifyingObject
' 转换为一个 Aspect。完成此步骤后,您的代码将如下所示
示例
/// <summary >
/// A Person-Class
/// </summary >
[Notifying]
public class Person : INotifyPropertyChanged
{
private string firstName;
private string lastName;
/// <summary >
/// Firstname of the Person
/// </summary >
public string FirstName
{
get { return firstName; }
[NotifyingDependency(DependencyProperty = "Name")]
set { firstName = value; }
}
/// <summary >
/// Lastname of the Person
/// </summary >
public string LastName
{
get { return lastName; }
[NotifyingDependency(DependencyProperty = "Name")]
set { lastName = value; }
}
/// <summary >
/// Complete Name
/// </summary >
public string Name
{
get { return FirstName + " " + LastName; }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
现在,让我们仔细看看几行代码
[Notifying]
public class Person : INotifyPropertyChanged
这会将 NotifyingAttribute
附加到声明类的每个属性的 set_
方法。当此方法退出时,将触发通知。NotifyingAttribute
只能附加到实现 INotifyPropertyChanged
接口的类。
[Notifying]
public class Person
上面的代码将导致一个 Postsharp 错误,如下所示:PostSharp: WindowsFormsApplication_3._5.Person 必须实现 INotifyPropertyChanged。
[NotifyingDependency(DependencyProperty = "Name")]
Name
属性依赖于 FirstName
和 LastName
。如果其中一个属性发生更改,将触发通知。具有传递名称的属性必须存在,否则您将看到一个 Postshar 错误,如下所示:PostSharp: WindowsFormsApplication_3._5.Person 没有属性 'ThirdName'。
代码列表
NotifyingAttribute
[Serializable]
[MulticastAttributeUsage(MulticastTargets.Method)]
public class NotifyingAttribute : OnMethodBoundaryAspect
{
private string[] propertyNames;
private FieldInfo typeFieldInfo;
/// <summary>
/// Do as much work as possible at the Compiletime
/// see: http://doc.postsharp.org/1.5/ClassRef/html/
// fbc0e32a-2617-c9ef-cb9e-45cdc171dde0.htm
/// </summary>
public override void CompileTimeInitialize(System.Reflection.MethodBase method)
{
// get the DependencyAttributes of the method
Attribute[] atts = Attribute.GetCustomAttributes(method,
typeof(NotifyingDependencyAttribute));
int attCount = atts.Count();
this.propertyNames = new string[attCount + 1];
// first Fired = Property
this.propertyNames[0] = GetPropertyNameFromMethodName(method.Name);
// Add the dependency properties
for (int i = 0; i < attCount; i++)
{
string depProp = (atts[i] as NotifyingDependencyAttribute).DependencyProperty;
this.propertyNames[i + 1] = (atts[i] as
NotifyingDependencyAttribute).DependencyProperty;
}
}
/// <summary>
/// Validating the Usage of NotifyingAttribute
/// see: http://doc.postsharp.org/1.5/ClassRef/html/
/// aa77e517-2c98-ebec-c0e1-6bb20d013c33.htm
/// </summary>
public override bool CompileTimeValidate(System.Reflection.MethodBase method)
{
// check on implemented INotifyPropertyChanged
if (method.DeclaringType.GetInterface(typeof(INotifyPropertyChanged).Name) != null)
{
// set_* are of interest
// IsSpecialName checked to avoid attaching to something
// like this: public void set_FooBar()
// Note:
// If somebody knows a Method, which set IsSpecialName
// to true (without property setters)
// -> leve a comment. thanks!
if (method.Name.StartsWith("set_") && method.IsSpecialName)
{
// check if NotifyingIgnoreAttribute is set
Attribute[] atts = Attribute.GetCustomAttributes(method,
typeof(NotifyingIgnoreAttribute));
return atts.Count() == 0;
}
}
else
{
// Create Postsharp Error
PostSharp.Extensibility.Message error =
new PostSharp.Extensibility.Message(SeverityType.Error,
"AOP0001", method.DeclaringType.ToString() +
" has to implement INotifyPropertyChanged", "#");
MessageSource.MessageSink.Write(error);
}
return false;
}
/// <summary>
/// Callback which is called after executing setter-code
/// see: http://doc.postsharp.org/1.5/ClassRef/html/
/// 4d72f4ea-11c5-cbce-b56b-b671c98cffaf.htm
/// </summary>
public override void OnExit(MethodExecutionEventArgs eventArgs)
{
// get Fieldinfo of event PropertyChangedEventHandler PropertyChanged
this.typeFieldInfo = eventArgs.Instance.GetType()
.GetFields(BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public)
.Where(f => f.FieldType ==
typeof(PropertyChangedEventHandler))
.FirstOrDefault();
// fire the Notify on the invoking Instance
FireNotify(eventArgs.Instance);
}
/// <summary>
/// Fire the Notifies on the invoking Instance
/// </summary>
/// <param name="Target">invoking Instance</param>
private void FireNotify(object Target)
{
// Fieldinfo of PropertyChanged found
if (this.typeFieldInfo != null)
{
// get the value of PropertyChanged
PropertyChangedEventHandler evHandler =
typeFieldInfo.GetValue(Target) as PropertyChangedEventHandler;
if (evHandler != null)
{
foreach (string prop in propertyNames)
{
// invoke the event
evHandler.Invoke(Target, new PropertyChangedEventArgs(prop));
}
}
}
}
/// <summary>
/// Gets the Property Name from a setter method name
/// </summary>
/// <param name="methodName">setter method name</param>
/// <returns>Property Name</returns>
private string GetPropertyNameFromMethodName(string methodName)
{
return methodName.Substring(4);
}
}
NotifyingDependencyAttribute
/// <summary>
/// Aspect for defining notify dependencies
/// </summary>
[Serializable]
public class NotifyingDependencyAttribute : OnMethodBoundaryAspect
{
public string DependencyProperty { get; set; }
/// <summary>
/// Validating the Usage of NotifyingDependencyAttribute
/// see: http://doc.postsharp.org/1.5/ClassRef/html/
/// aa77e517-2c98-ebec-c0e1-6bb20d013c33.htm
/// </summary>
public override bool CompileTimeValidate(MethodBase method)
{
// Dependency Property must exists
if (!PropertyHelper.PropertyExists(method.DeclaringType, DependencyProperty))
{
// Create Postsharp Error
PostSharp.Extensibility.Message error =
new PostSharp.Extensibility.Message(SeverityType.Error,
"AOP0002", method.DeclaringType.ToString() +
" has no Property '" + DependencyProperty +
"'", "#");
MessageSource.MessageSink.Write(error);
return false;
}
return true;
}
}
NotifyingDependencyAttribute
此类可用于接受来自通知对象的属性。
/// <summary>
/// Aspect to except Setters of Properties of Classes marked with [Notifying]
/// </summary>
[Serializable]
public class NotifyingIgnoreAttribute : Attribute { }
HelpClass
public class PropertyHelper
{
public static bool PropertyExists(Type t, string propertyName)
{
return t.GetProperty(propertyName) != null;
}
}