Обработать обработчики событий изменения свойств (их много) более элегантно (словарь?) - PullRequest
5 голосов
/ 16 марта 2010

ПРИВЕТ!

Здесь у меня есть простой пример класса с тремя полями типа 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 ]

2 голосов
/ 16 марта 2010

Самый элегантный способ, который я знаю, это использовать Аспектно-ориентированное программирование (AOP) с таким инструментом, как PostSharp . Я нашел примеры реализации INotifyPropertyChanged здесь и здесь . Это позволяет вам украшать ваши свойства с помощью атрибута, а PostSharp затем реализует INotifyPropertyChanged для вас, когда код создается.

1 голос
/ 13 сентября 2012

Похоже, вы настраиваете цепочку зависимостей.Ни одно из решений АОП или статического анализа не справится с этим должным образом.Ознакомьтесь с Update Controls, который использует отслеживание зависимостей для обнаружения цепочек зависимостей во время выполнения .

Вот что ваш пример становится:

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; }
    }
}
0 голосов
/ 05 сентября 2011

Инструмент, который вы можете рассмотреть, это T4 (T4 поставляется с VS2010, поэтому никаких дополнительных зависимостей не требуется).

T4 - это инструмент для генерации кода, который может помочь уменьшить поддержку «утомительного» кода. Подумайте ASP / PHP для кода. Это также похоже на XML / XSLT.

Отличное введение в T4 можно найти в блоге Олег Сыч .

Преимущества генерации кода в подобном случае заключаются в том, что, хотя сгенерированный код является избыточным, поддерживаемый вами код (шаблон 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 ();
        // --------------------------------------------------------------------
    }
    // ------------------------------------------------------------------------
}
0 голосов
/ 16 марта 2010

Чтобы немного упростить, вы можете сделать следующие две вещи.

Во-первых, в обработчике PropertyChanged первый параметр sender - это объект, который вызвал событие, по крайней мере, если вы внедрили OnPropertyChanged в классе B так же, как в классе A. Это означает, что вам нужен только один обработчик для всех свойств 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, мы можем перейти к написанию установщика свойств, например:

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;
}

, куда вы можете отправить метод прослушивания, который вы хотели бы использовать в каждом случае.

0 голосов
/ 16 марта 2010

Один хороший способ упростить настройку ваших свойств можно найти здесь .

Относительно ваших каскадных уведомлений: я полагаю, что вы могли бы использовать описанный выше подход для обработки(не) подписка на события для свойств, реализующих INotifyPropertyChanged там.

...