更优雅地处理属性更改事件侦听器(很多)(字典?)

时间:2010-03-16 11:05:49

标签: c# inotifypropertychanged

你好!

这里我有一个简单的类示例,其中包含三个类型为B类的字段以及其他一些内容。 正如你所看到我正在监听每个孩子对象的变化。 因为我可能需要很多类型为B的属性,我想知道是否有一种缩小代码的方法。为每个人创建一个监听器+一个方法似乎我会有很多代码。我如何解决这个...使用字典或类似的东西?我被告知IoC可以解决这个问题,但我不确定从哪里开始。

public class A : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public int _id;
    public int Id
    {
        get { return _id; }
        set
        {
            if (_id == value)
            {
                return;
            }

            _id = value;
            OnPropertyChanged("Id"); 
        }
    }

    public string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name == value)
            {
                return;
            }

            _name = value; 
            OnPropertyChanged("Name"); 
        }
    }

    public B _firstB;
    public B FirstB
    {
        get { return _firstB; }
        set 
        {
            if (_firstB == value)
            {
                return;
            }

            if (_firstB != null)
            {
                FirstB.PropertyChanged -= firstObjectB_Listener;
            }

            _firstB = value;

            if (_firstB != null) 
                FirstB.PropertyChanged += new PropertyChangedEventHandler(firstObjectB_Listener);

            OnPropertyChanged("FirstB"); 
        }
    }

    public B _secondB;
    public B SecondB
    {
        get { return _secondB; }
        set
        {
            if (_secondB == value)
            {
                return;
            }

            if (_secondB != null)
            {
                FirstB.PropertyChanged -= secondObjectB_Listener;
            }

            _secondB = value;

            if (_secondB != null)
                SecondB.PropertyChanged += new PropertyChangedEventHandler(secondObjectB_Listener);

            OnPropertyChanged("FirstB");
        }
    }

    public B _thirdB;
    public B ThirdB
    {
        get { return _thirdB; }
        set
        {
            if (_thirdB == value)
            {
                return;
            }

            if (_thirdB != null)
            {
                ThirdB.PropertyChanged -= thirdObjectB_Listener;
            }

            _thirdB = value;

            if (_thirdB != null)
                ThirdB.PropertyChanged += new PropertyChangedEventHandler(thirdObjectB_Listener);

            OnPropertyChanged("ThirdB");
        }
    }

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

    void firstObjectB_Listener(object sender, PropertyChangedEventArgs e)
    {
        Console.WriteLine("Object A has found a change of " + e.PropertyName + " on first object B");
    }

    void secondObjectB_Listener(object sender, PropertyChangedEventArgs e)
    {
        Console.WriteLine("Object A has found a change of " + e.PropertyName + " on second object B");
    }

    void thirdObjectB_Listener(object sender, PropertyChangedEventArgs e)
    {
        Console.WriteLine("Object A has found a change of " + e.PropertyName + " on third object B");
    }
}

5 个答案:

答案 0 :(得分:2)

我所知道的最优雅的方法是使用面向方面编程(AOP)和PostSharp等工具。我找到了INotifyPropertyChanged实现示例herehere。这些允许您使用属性装饰属性,然后PostSharp在构建代码时为您实现INotifyPropertyChanged。

答案 1 :(得分:1)

看起来您正在设置依赖关系链。 AOP或静态分析解决方案都没有适当地处理这个问题。查看更新控件,它使用依赖关系跟踪到discover dependency chains at runtime

以下是您的示例:

public class B
{
    private Independent<string> _someProperty = new Independent<string>();

    public string SomeProperty
    {
        get { return _someProperty; }
        set { _someProperty.Value = value; }
    }
}

public class A
{
    private Dependent<string> _dependentProperty;

    public A()
    {
        _dependentProperty = new Dependent<string>(() =>
            FirstB.SomeProperty + ", " + SecondB.SomeProperty + ", " + ThirdB.SomeProperty);
    }

    public string DependentProperty
    {
        get { return _dependentProperty; }
    }

    private Independent<int> _id = new Independent<int>();
    public int Id
    {
        get { return _id; }
        set { _id.Value = value; }
    }

    private Independent<string> _name = new Independent<string>();
    public string Name
    {
        get { return _name; }
        set { _name.Value = value; }
    }

    private Independent<B> _firstB = new Independent<B>();
    public B FirstB
    {
        get { return _firstB; }
        set { _firstB.Value = value; }
    }

    private Independent<B> _secondB = new Independent<B>();
    public B SecondB
    {
        get { return _secondB; }
        set { _secondB.Value = value; }
    }

    private Independent<B> _thirdB = new Independent<B>();
    public B ThirdB
    {
        get { return _thirdB; }
        set { _thirdB.Value = value; }
    }
}

答案 2 :(得分:0)

可以找到一种简化属性设置的好方法here

关于您的级联通知:我猜您可以使用上面概述的方法来处理(非)订阅在那里实现INotifyPropertyChanged的属性的事件。

答案 3 :(得分:0)

为了简化一点,你可以做以下两件事。

首先,在PropertyChanged的处理程序中,第一个参数sender是触发事件的对象,至少如果你在B类中以与A类相同的方式实现了OnPropertyChanged,这意味着你只需要一个所有B属性的处理程序。

private void BValueListener(object sender, PropertyChangedEventArgs e)
{
  Console.WriteLine("Found change of {0} on object {1}", e.PropertyName, sender);
}

如果您需要确切知道发送哪个B属性,您可以在BValueListener方法中进行检查。

if (sender == FirstB) { /* Do special stuff here */ }

对于所有B属性具有相同的侦听器,我们可以继续编写属性setter,如:

private B _thirdB;
public B ThirdB
{
  get { return _thirdB; }
  set {
    if (UpdateBValue(ref _thirdB, value)) {
      OnPropertyChanged("ThirdB");
    }
  }
}
private bool UpdateBValue(ref B value, B newValue)
{
  if (value == newValue)
  {
    return false;
  }

  if (value != null)
  {
    value.PropertyChanged -= BValueListener;
  }

  value = newValue;
  if (value != null)
  {
    value.PropertyChanged += BValueListener;
  }
  return true;
}

如果你真的需要为每个属性使用不同的处理程序,你可以将上面的代码修改为类似

private B _thirdB;
public B ThirdB
{
    get { return _thirdB; }
    set 
    { 
        if (UpdateBValue(ref _thirdB, value, BValueListener))
        {
            OnPropertyChanged("ThirdB");
        }
    }
} 

private bool UpdateBValue(ref B value, B newValue, PropertyChangedEventHandler eventHandler)
{
    if (value == newValue)
    {
        return false;
    }

    if (value != null)
    {
        value.PropertyChanged -= eventHandler;
    }

    value = newValue;

    if (value != null)
    {
        value.PropertyChanged += eventHandler;
    }
    return true;
}

您可以在每种情况下发送您想要使用的侦听器方法。

答案 4 :(得分:0)

您可以考虑使用的工具是T4(T4随VS2010一起提供,因此不需要额外的依赖项。)

T4是一种代码生成工具,可以帮助减少“繁琐”代码的维护。想想ASP / PHP代码。它也类似于XML / XSLT。

有关T4的精彩介绍,请参阅Oleg Sychs博客。

在这种情况下代码生成的好处是,即使生成的代码是冗余的,您维护的代码(T4模板)也不会或至少不那么多余。

考虑一下你提供的样本,我写了这个T4模板: (如果要在Visual Studio中尝试此模板,请单击“添加新项”,选择类模板,但将扩展名从.cs更改为.tt,将以下源粘贴到.tt文件中并保存。保存后,结果应该在相应的.cs文件)

// ReSharper disable InconsistentNaming
// ReSharper disable PartialMethodWithSinglePart
// ReSharper disable PartialTypeWithSinglePart
<#
    // This is the "model" that is "what" we would like to generate
    var classDefs = new []
    {
        new ClassDefinition
        {
            Name = "A",
            Properties = new []
            {
                P ("int"    , "Id"      ),
                P ("string" , "Name"    ),
                P ("B"      , "FirstB"  , listenToChanges:true  ),
                P ("B"      , "SecondB" , listenToChanges:true  ),
                P ("B"      , "ThirdB"  , listenToChanges:true  ),
            },
        },
        new ClassDefinition
        {
            Name = "B",
            Properties = new []
            {
                P ("int"    , "Id"      ),
                P ("string" , "Name"    ),
            },
        },
    };
#>

namespace SO
{
    using System;
    using System.ComponentModel;

<#
    // This part is the template ie "how" the model will be transformed into code
    foreach (var classDef in classDefs)
    {
#>        

    // ------------------------------------------------------------------------
    /// <summary>
    /// class <#=classDef.Name#> (implements INotifyPropertyChanged)
    /// </summary>
    public partial class <#=classDef.Name#> : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        void OnPropertyChanged (string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) 
            {
                handler (this, new PropertyChangedEventArgs (name));
            }
        }

<#
        foreach (var propertyDef in classDef.Properties)
        {
#>

        // --------------------------------------------------------------------
        /// <summary>
        /// Gets or sets property <#=propertyDef.Name#> (<#=propertyDef.Type#>)
        /// </summary>
        public <#=propertyDef.Type#> <#=propertyDef.Name#>
        { 
            get { return <#=propertyDef.FieldName#>; } 
            set 
            { 
                if (<#=propertyDef.FieldName#> == value) 
                { 
                    return; 
                } 

<#
            if (propertyDef.ListenToChanges)
            {
#>
                if (<#=propertyDef.FieldName#> != null) 
                { 
                    <#=propertyDef.FieldName#>.PropertyChanged -= <#=propertyDef.ListenerName#>;
                } 

                <#=propertyDef.FieldName#> = value; 

                if (<#=propertyDef.FieldName#> != null)  
                {
                    <#=propertyDef.FieldName#>.PropertyChanged += <#=propertyDef.ListenerName#>; 
                }
<#
            }
            else
            {
#>
                <#=propertyDef.FieldName#> = value; 
<#
            }
#>

                <#=propertyDef.EventName#> ();
                OnPropertyChanged("<#=propertyDef.Name#>");  

            } 
        } 
        // --------------------------------------------------------------------
        <#=propertyDef.Type#> <#=propertyDef.FieldName#>; 
        // --------------------------------------------------------------------
        partial void <#=propertyDef.EventName#> ();
        // --------------------------------------------------------------------
<#
            if (propertyDef.ListenToChanges)
            {
#>
        void <#=propertyDef.ListenerName#> (object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine (
                "Instance of <#=classDef.Name#> detected a change of <#=propertyDef.Name#>.{0}", 
                e.PropertyName
                );
            <#=propertyDef.EventName#> ();
        }
        // --------------------------------------------------------------------
<#
            }
        }
#>
    }
    // ------------------------------------------------------------------------
<#
    }
#>
}
<#+
    class ClassDefinition
    {
        public string Name;
        public PropertyDefinition[] Properties;
    }

    class PropertyDefinition
    {
        public string Type;
        public string Name;
        public bool ListenToChanges;

        public string FieldName 
        {
            get
            {
                return "_" + Name;
            }
        }

        public string ListenerName 
        {
            get
            {
                return Name + "_Listener";
            }
        }

        public string EventName 
        {
            get
            {
                return "Change_" + Name;
            }
        }
    }

    PropertyDefinition P (string type, string name, bool listenToChanges = false)
    {
        return new PropertyDefinition
            {
                Type = type ?? "<NO_TYPE>",
                Name = name ?? "<NO_NAME>",
                ListenToChanges = listenToChanges,
            };
    }
#>

最后这会产生这个输出:

// ReSharper disable InconsistentNaming
// ReSharper disable PartialMethodWithSinglePart
// ReSharper disable PartialTypeWithSinglePart

namespace SO
{
    using System;
    using System.ComponentModel;



    // ------------------------------------------------------------------------
    /// <summary>
    /// class A (implements INotifyPropertyChanged)
    /// </summary>
    public partial class A : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        void OnPropertyChanged (string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) 
            {
                handler (this, new PropertyChangedEventArgs (name));
            }
        }


        // --------------------------------------------------------------------
        /// <summary>
        /// Gets or sets property Id (int)
        /// </summary>
        public int Id
        { 
            get { return _Id; } 
            set 
            { 
                if (_Id == value) 
                { 
                    return; 
                } 

                _Id = value; 

                Change_Id ();
                OnPropertyChanged("Id");  

            } 
        } 
        // --------------------------------------------------------------------
        int _Id; 
        // --------------------------------------------------------------------
        partial void Change_Id ();
        // --------------------------------------------------------------------

        // --------------------------------------------------------------------
        /// <summary>
        /// Gets or sets property Name (string)
        /// </summary>
        public string Name
        { 
            get { return _Name; } 
            set 
            { 
                if (_Name == value) 
                { 
                    return; 
                } 

                _Name = value; 

                Change_Name ();
                OnPropertyChanged("Name");  

            } 
        } 
        // --------------------------------------------------------------------
        string _Name; 
        // --------------------------------------------------------------------
        partial void Change_Name ();
        // --------------------------------------------------------------------

        // --------------------------------------------------------------------
        /// <summary>
        /// Gets or sets property FirstB (B)
        /// </summary>
        public B FirstB
        { 
            get { return _FirstB; } 
            set 
            { 
                if (_FirstB == value) 
                { 
                    return; 
                } 

                if (_FirstB != null) 
                { 
                    _FirstB.PropertyChanged -= FirstB_Listener;
                } 

                _FirstB = value; 

                if (_FirstB != null)  
                {
                    _FirstB.PropertyChanged += FirstB_Listener; 
                }

                Change_FirstB ();
                OnPropertyChanged("FirstB");  

            } 
        } 
        // --------------------------------------------------------------------
        B _FirstB; 
        // --------------------------------------------------------------------
        partial void Change_FirstB ();
        // --------------------------------------------------------------------
        void FirstB_Listener (object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine (
                "Instance of A detected a change of FirstB.{0}", 
                e.PropertyName
                );
            Change_FirstB ();
        }
        // --------------------------------------------------------------------

        // --------------------------------------------------------------------
        /// <summary>
        /// Gets or sets property SecondB (B)
        /// </summary>
        public B SecondB
        { 
            get { return _SecondB; } 
            set 
            { 
                if (_SecondB == value) 
                { 
                    return; 
                } 

                if (_SecondB != null) 
                { 
                    _SecondB.PropertyChanged -= SecondB_Listener;
                } 

                _SecondB = value; 

                if (_SecondB != null)  
                {
                    _SecondB.PropertyChanged += SecondB_Listener; 
                }

                Change_SecondB ();
                OnPropertyChanged("SecondB");  

            } 
        } 
        // --------------------------------------------------------------------
        B _SecondB; 
        // --------------------------------------------------------------------
        partial void Change_SecondB ();
        // --------------------------------------------------------------------
        void SecondB_Listener (object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine (
                "Instance of A detected a change of SecondB.{0}", 
                e.PropertyName
                );
            Change_SecondB ();
        }
        // --------------------------------------------------------------------

        // --------------------------------------------------------------------
        /// <summary>
        /// Gets or sets property ThirdB (B)
        /// </summary>
        public B ThirdB
        { 
            get { return _ThirdB; } 
            set 
            { 
                if (_ThirdB == value) 
                { 
                    return; 
                } 

                if (_ThirdB != null) 
                { 
                    _ThirdB.PropertyChanged -= ThirdB_Listener;
                } 

                _ThirdB = value; 

                if (_ThirdB != null)  
                {
                    _ThirdB.PropertyChanged += ThirdB_Listener; 
                }

                Change_ThirdB ();
                OnPropertyChanged("ThirdB");  

            } 
        } 
        // --------------------------------------------------------------------
        B _ThirdB; 
        // --------------------------------------------------------------------
        partial void Change_ThirdB ();
        // --------------------------------------------------------------------
        void ThirdB_Listener (object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine (
                "Instance of A detected a change of ThirdB.{0}", 
                e.PropertyName
                );
            Change_ThirdB ();
        }
        // --------------------------------------------------------------------
    }
    // ------------------------------------------------------------------------


    // ------------------------------------------------------------------------
    /// <summary>
    /// class B (implements INotifyPropertyChanged)
    /// </summary>
    public partial class B : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        void OnPropertyChanged (string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) 
            {
                handler (this, new PropertyChangedEventArgs (name));
            }
        }


        // --------------------------------------------------------------------
        /// <summary>
        /// Gets or sets property Id (int)
        /// </summary>
        public int Id
        { 
            get { return _Id; } 
            set 
            { 
                if (_Id == value) 
                { 
                    return; 
                } 

                _Id = value; 

                Change_Id ();
                OnPropertyChanged("Id");  

            } 
        } 
        // --------------------------------------------------------------------
        int _Id; 
        // --------------------------------------------------------------------
        partial void Change_Id ();
        // --------------------------------------------------------------------

        // --------------------------------------------------------------------
        /// <summary>
        /// Gets or sets property Name (string)
        /// </summary>
        public string Name
        { 
            get { return _Name; } 
            set 
            { 
                if (_Name == value) 
                { 
                    return; 
                } 

                _Name = value; 

                Change_Name ();
                OnPropertyChanged("Name");  

            } 
        } 
        // --------------------------------------------------------------------
        string _Name; 
        // --------------------------------------------------------------------
        partial void Change_Name ();
        // --------------------------------------------------------------------
    }
    // ------------------------------------------------------------------------
}