Удаление PropertyChanged-item из BindingList приводит к сбросу списка

Определяемый пользователем класс наследуется от INotifyPropertyChanged.

В пользовательском классе некоторое свойство передает событие PropertyChanged.

Во время этого события сам объект удаляется из BindingList.

Событие продолжает выполнение, и BindingList получает событие ListChangedType.Reset.

Что можно сделать, чтобы избежать события сброса?


person TarmoPikaro    schedule 18.09.2015    source источник


Ответы (1)


(Не видел ответа на этот вопрос, поэтому решил добавить оба - вопрос и ответ)

Google по запросу "BindingList Child_PropertyChanged"

дает следующий фрагмент кода:

http://referencesource.microsoft.com/#System/compmod/system/componentmodel/BindingList.cs,e757be5fba0e6000,references

Означает, что если событие получено BindingList, а данного элемента нет в BindingList - это вызовет сброс списка.

Но поскольку PropertyChanged.Invoke собирает весь список событий перед запуском трансляции перед выполнением - событие будет вызвано в любом случае независимо от того, находится ли элемент в списке или нет.

Позвольте следующему фрагменту кода продемонстрировать ошибку:

Program.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Reflection;
using System.Security;

namespace Eventing
{

    public class ThisItem : MulticastNotifyPropertyChanged
    {
        void Test()
        {
        }

        String _name;
        public String name2
        {
            get {
                return _name;
            }

            set
            {
                _name = value;

                Console.WriteLine("---------------------------");
                Console.WriteLine("Invoke test #2");
                Console.WriteLine("---------------------------");
                Invoke(this, new PropertyChangedEventArgs("name"));
            }
        }

        public String name1
        {
            get {
                return _name;
            }

            set
            {
                _name = value;
#if TESTINVOKE
                Console.WriteLine("---------------------------");
                Console.WriteLine("Invoke test #1");
                Console.WriteLine("---------------------------");
                InvokeFast(this, new PropertyChangedEventArgs("name"));
#endif
            }
        }
    };

    class Program
    {
        static public BindingList<ThisItem> testList;

        static void Main(string[] args)
        {
            testList = new BindingList<ThisItem>();
            ThisItem t = new ThisItem();
            testList.ListChanged += testList_ListChanged;

            t.PropertyChanged += t_PropertyChanged;
            t.PropertyChanged += t_PropertyChanged2;
            testList.Add(t);
            t.name1 = "testing";

            Console.WriteLine("---------------------------");
            t.PropertyChanged -= t_PropertyChanged;
            t.PropertyChanged -= t_PropertyChanged2;
            t.PropertyChanged += t_PropertyChanged;
            testList.Add(t);
            t.PropertyChanged += t_PropertyChanged2;

            t.name2 = "testing";
        }

        static void testList_ListChanged(object sender, ListChangedEventArgs e)
        {
            Console.WriteLine("3) List changed: " + e.ListChangedType.ToString() + ((e.ListChangedType == ListChangedType.Reset) ? " (*** UPS! ***)": ""));
        }

        static void t_PropertyChanged2(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine("2) t_PropertyChanged2: " + e.PropertyName);
        }

        static void t_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine("1) t_PropertyChanged: " + e.PropertyName);
            testList.Remove((ThisItem)sender);
        }
    }
}

MulticastNotifyPropertyChanged.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace System
{
    /// <summary>
    /// class which implements INotifyPropertyChanged in such manner that event can be broadcasted in safe manner - 
    /// even if given item is removed from BindingList, event in BindingList (Child_PropertyChanged) won't be 
    /// triggered.
    /// </summary>
    public class MulticastNotifyPropertyChanged : INotifyPropertyChanged
    {
        /// <summary>
        /// List of all registered events. List can change during event broadcasting.
        /// </summary>
        List<PropertyChangedEventHandler> _PropertyChangedHandlers = new List<PropertyChangedEventHandler>();

        /// <summary>
        /// Next broadcasted event index.
        /// </summary>
        int iFuncToInvoke;
    #if TESTINVOKE
            PropertyChangedEventHandler _PropertyChangedAllInOne;
    #endif

        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
        {
            add
            {
    #if TESTINVOKE
                    _PropertyChangedAllInOne += value;
    #endif
                _PropertyChangedHandlers.Add(value);
            }
            remove
            {
    #if TESTINVOKE
                    _PropertyChangedAllInOne -= value;
    #endif
                int index = _PropertyChangedHandlers.IndexOf(value);

                if (index == -1)
                    return;

                if (iFuncToInvoke >= index)     //Scroll back event iterator if needed.
                    iFuncToInvoke--;

    #if TESTINVOKE
                    Console.WriteLine("Unregistering event. Iterator value: " + iFuncToInvoke.ToString());
    #endif
                _PropertyChangedHandlers.Remove(value);
            }
        }

        /// <summary>
        /// Just an accessor, so no cast would be required on client side.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                ((INotifyPropertyChanged)this).PropertyChanged += value;
            }
            remove
            {
                ((INotifyPropertyChanged)this).PropertyChanged -= value;
            }
        }

        /// <summary>
        /// Same as normal Invoke, except this plays out safe - if item is removed from BindingList during event broadcast -
        /// event won't be fired in removed item direction.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void Invoke(object sender, PropertyChangedEventArgs e)
        {
            for (iFuncToInvoke = 0; iFuncToInvoke < _PropertyChangedHandlers.Count; iFuncToInvoke++)
            {
    #if TESTINVOKE
                    Console.WriteLine("Invoke: " + iFuncToInvoke.ToString());
    #endif
                _PropertyChangedHandlers[iFuncToInvoke].Invoke(sender, e);
            }
        }

    #if TESTINVOKE
            public void InvokeFast( object sender, PropertyChangedEventArgs e )
            {
                _PropertyChangedAllInOne.Invoke(sender, e);
            }
    #endif
    }

} //namespace System

Результатом будет следующий поток выполнения:

3) List changed: ItemAdded
---------------------------
Invoke test #1
---------------------------
1) t_PropertyChanged: name
Unregistering event. Iterator value: 0
3) List changed: ItemDeleted
2) t_PropertyChanged2: name
3) List changed: Reset (*** UPS! ***)
---------------------------
Unregistering event. Iterator value: -1
Unregistering event. Iterator value: -1
3) List changed: ItemAdded
---------------------------
Invoke test #2
---------------------------
Invoke: 0
1) t_PropertyChanged: name
Unregistering event. Iterator value: 0
3) List changed: ItemDeleted
Invoke: 1
2) t_PropertyChanged2: name

Это решило событие списка, однако оно может создать больше проблем с добавлением элементов в событие BindingList durign. Этот код, вероятно, мы также можем исправить, чтобы не транслировать события на новые добавленные элементы (как это делает обычный BindingList), но это то, что вы можете решить самостоятельно, если это необходимо.

person TarmoPikaro    schedule 18.09.2015