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

近乎自动的 INotifyPropertyChanged、自动的 IsDirty 以及自动的 ChangeTracking

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.42/5 (12投票s)

2009年8月26日

Ms-PL

2分钟阅读

viewsIcon

72122

downloadIcon

818

一种易于重构的 INotifyPropertyChanged 实现,无需使用反射。

引言

WPF… 感觉我需要为我尝试做的一切都构建变通方法。在 WPF 之前,我很少需要 INotifyPropertyChanged

但既然我们需要它,为什么不充分利用它呢。由于我想要一个所有未来对象的基础类,在所有未来的项目中,我想要一个非常简洁的东西。

  • 以最佳方式实现 INotifyPropertyChanged(易于重构且性能最佳)
  • 自动设置 IsDirty 标志
  • 自动更改跟踪,以便记录已更改的内容(就像在 SharePoint 中获取警报时一样,我喜欢那样!)
  • 更改跟踪也应导出为文本,以便我可以将其写入 SQL

当然,我不用告诉你,代码必须始终确保最少的工作量。而且,它应该是易于重构的等等… 我想要完美的代码。

代码

/// <summary>
/// This object automatically implements notify property changed
/// </summary>
public class NotifyPropertyChangeObject : INotifyPropertyChanged
{
    /// <summary>
    /// Track changes or not.
    /// If we're working with DTOs and we fill up the DTO
    /// in the DAL we should not be tracking changes.
    /// </summary>
    private bool trackChanges = false;
    /// <summary>
    /// Changes to the object
    /// </summary>
    public Dictionary<string, object> Changes { get; private set; }
    /// <summary>
    /// Is the object dirty or not?
    /// </summary>
    public bool IsDirty
    {
        get { return Changes.Count > 0; }
        set { ; }
    }
    /// <summary>
    /// Event required for INotifyPropertyChanged
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// This constructor will initialize the change tracking
    /// </summary>
    public NotifyPropertyChangeObject()
    {
        // Change tracking default
        trackChanges = true;
        // New change tracking dictionary
        Changes = new Dictionary<string, object>();
    }
    /// <summary>
    /// Reset the object to non-dirty
    /// </summary>
    public void Reset()
    {
        Changes.Clear();
    }
    /// <summary>
    /// Start tracking changes
    /// </summary>
    public void StartTracking()
    {
        trackChanges = true;
    }
    /// <summary>
    /// Stop tracking changes
    /// </summary>
    public void StopTracking()
    {
        trackChanges = false;
    }
    /// <summary>
    /// Change the property if required and throw event
    /// </summary>
    /// <param name="variable"></param>
    /// <param name="property"></param>
    /// <param name="value"></param>
    public void ApplyPropertyChange<T, F>(ref F field, 
                Expression<Func<T, object>> property, F value)
    {
        // Only do this if the value changes
        if (field == null || !field.Equals(value))
        {
            // Get the property
            var propertyExpression = GetMemberExpression(property);
            if (propertyExpression == null)
                throw new InvalidOperationException("You must specify a property");
            // Property name
            string propertyName = propertyExpression.Member.Name;
            // Set the value
            field = value;
            // If change tracking is enabled, we can track the changes...
            if (trackChanges)
            {
                // Change tracking
                Changes[propertyName] = value;
                // Notify change
                NotifyPropertyChanged(propertyName);
            }
        }
    }
    /// <summary>
    /// Get member expression
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expression"></param>
    /// <returns></returns>
    public MemberExpression GetMemberExpression<T>(Expression<Func<T, 
                            object>> expression)
    {
        // Default expression
        MemberExpression memberExpression = null;
        // Convert
        if (expression.Body.NodeType == ExpressionType.Convert)
        {
            var body = (UnaryExpression)expression.Body;
            memberExpression = body.Operand as MemberExpression;
        }
        // Member access
        else if (expression.Body.NodeType == ExpressionType.MemberAccess)
        {
            memberExpression = expression.Body as MemberExpression;
        }
        // Not a member access
        if (memberExpression == null)
            throw new ArgumentException("Not a member access", 
                                        "expression");
        // Return the member expression
        return memberExpression;
    }
    /// <summary>
    /// The property has changed
    /// </summary>
    /// <param name="propertyName"></param>
    private void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    /// <summary>
    /// Convert the changes to an XML string
    /// </summary>
    /// <returns></returns>
    public string ChangesToXml()
    {
        // Prepare base objects
        XDeclaration declaration = new XDeclaration("1.0", 
                                       Encoding.UTF8.HeaderName, String.Empty);
        XElement root = new XElement("Changes");
        // Create document
        XDocument document = new XDocument(declaration, root);
        // Add changes to the document
        // TODO: If it's an object, maybe do some other things
        foreach (KeyValuePair<string, object> change in Changes)
            root.Add(new XElement(change.Key, change.Value));
        // Get the XML
        return document.Document.ToString();
    }
}

使用代码

public class Person : NotifyPropertyChangeObject
{
    private string firstName;
    private string lastName;
    private int age;
    /// <summary>
    /// FirstName 
    /// </summary>
    [DefaultValue("")]
    public string FirstName
    {
        get { return firstName; }
        set { ApplyPropertyChange<Person, 
              string>(ref firstName, o => o.FirstName, value); }
    }
    
    /// <summary>
    /// LastName 
    /// </summary>
    [DefaultValue("")]
    public string LastName
    {
        get { return lastName; }
        set { ApplyPropertyChange<Person, 
              string>(ref lastName, o => o.LastName, value); }
    }
    /// <summary>
    /// Age 
    /// </summary>
    [DefaultValue(0)]
    public int Age
    {
        get { return age; }
        set { ApplyPropertyChange<Person, int>(ref age, o => o.Age, value); }
    }
}

就这样了…

关于代码的一些说明

  • 我们的类 Person 继承自 NotifyPropertyChangeObject
  • 当我们更改属性的值时,神奇的事情发生了。
  • 我们将 私有变量属性(易于重构)和 新值 传递过去。
  • 之后,我们检查“新”值是否与旧值匹配。如果是这样,则不应发生任何事情。
  • 如果值不匹配,我们想要更改该值并通知发生了更改。
  • 同时,我们还将该更改添加到 Changes 字典中。
  • 可以安全地假设,如果字典包含更改,则对象是脏的。

最后,还有 ResetStartTrackingStopTracking 方法。这些与更改跟踪有关。

如果我在 DAL 中填充 DTO,我不想将其标记为脏。所以在开始之前,我调用 StopTracking,完成时,我调用 StartTracking

稍后,如果我保存对象,我希望它是干净的(不是脏的),所以我调用 Reset 方法。

最后,您可以调用 ChangesToXml 以获取所有更改的字符串表示形式。然后可以将其写入 SQL 或其他…

示例应用程序

在文章的顶部,您可以下载一个可用的项目。我还添加了一个示例,用于无法继承 NotifyPropertyChangeObject 的情况,例如在使用 Entity Framework 或 LINQ to SQL 时…

© . All rights reserved.