65.9K
CodeProject 正在变化。 阅读更多。
Home

AOP 实现 INotifyPropertyChanged

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.25/5 (7投票s)

2009年4月11日

CPOL

2分钟阅读

viewsIcon

49746

downloadIcon

254

对于懒人来说,如何实现 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 属性依赖于 FirstNameLastName。如果其中一个属性发生更改,将触发通知。具有传递名称的属性必须存在,否则您将看到一个 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;
    }
}
© . All rights reserved.