No more Death March

あるSEのチラシの裏 C# WPF

C# ObservableCollectionに対する操作とCollectionChangedイベントの内容

INotifyCollectionChangedインターフェースのCollectionChangedイベントについてMSDNの記述だけだとどういう通知が来るのかわからなかったのでプログラムを書いて確認してみた。

MSDNのページは以下

docs.microsoft.com

確認用のプログラム

Visual Studioで新規のコンソールアプリケーション作ってProgram.csにべたっとペーストすれば動く

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace CollectionChangedTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var col = new ObservableCollection<int>();
            col.CollectionChanged += Col_CollectionChanged;
            Debug.WriteLine("var col = new ObservableCollection<int>()");

            Debug.WriteLine("==========================================");
            Debug.WriteLine("col.Add(1)");
            Debug.WriteLine("==========================================");
            col.Add(1);
            Debug.WriteLine(string.Format("col = {0}", string.Join(",", col.Select(x => x.ToString()))));

            Debug.WriteLine("==========================================");
            Debug.WriteLine("col.Add(2)");
            Debug.WriteLine("==========================================");
            col.Add(2);
            Debug.WriteLine(string.Format("col = {0}", string.Join(",", col.Select(x => x.ToString()))));

            Debug.WriteLine("==========================================");
            Debug.WriteLine("col.Add(3)");
            Debug.WriteLine("==========================================");
            col.Add(3);
            Debug.WriteLine(string.Format("col = {0}", string.Join(",", col.Select(x => x.ToString()))));

            Debug.WriteLine("==========================================");
            Debug.WriteLine("col.Add(4)");
            Debug.WriteLine("==========================================");
            col.Add(4);
            Debug.WriteLine(string.Format("col = {0}", string.Join(",",col.Select(x => x.ToString()))));

            Debug.WriteLine("==========================================");
            Debug.WriteLine("col.Remove(3)");
            Debug.WriteLine("==========================================");
            col.Remove(3);
            Debug.WriteLine(string.Format("col = {0}", string.Join(",", col.Select(x => x.ToString()))));

            Debug.WriteLine("==========================================");
            Debug.WriteLine("col.RemoveAt(1)");
            Debug.WriteLine("==========================================");
            col.RemoveAt(1);
            Debug.WriteLine(string.Format("col = {0}", string.Join(",", col.Select(x => x.ToString()))));

            Debug.WriteLine("==========================================");
            Debug.WriteLine("col.Move(0,1)");
            Debug.WriteLine("==========================================");
            col.Move(0,1);
            Debug.WriteLine(string.Format("col = {0}", string.Join(",", col.Select(x => x.ToString()))));

            Debug.WriteLine("==========================================");
            Debug.WriteLine("col.Insert(1,5)");
            Debug.WriteLine("==========================================");
            col.Insert(1, 5);
            Debug.WriteLine(string.Format("col = {0}", string.Join(",", col.Select(x => x.ToString()))));

            Debug.WriteLine("==========================================");
            Debug.WriteLine("col.OrderBy(x => x)");
            Debug.WriteLine("==========================================");
            Debug.WriteLine("※異なる参照が返るのでイベントは発生しない。");
            col.OrderBy(x => x);
            Debug.WriteLine(string.Format("col = {0}", string.Join(",", col.Select(x => x.ToString()))));

            Debug.WriteLine("==========================================");
            Debug.WriteLine("col.Reverse()");
            Debug.WriteLine("==========================================");
            Debug.WriteLine("※異なる参照が返るのでイベントは発生しない。");
            col.Reverse();
            Debug.WriteLine(string.Format("col = {0}", string.Join(",", col.Select(x => x.ToString()))));

            Debug.WriteLine("==========================================");
            Debug.WriteLine("col.Clear()");
            Debug.WriteLine("==========================================");
            col.Clear();
            Debug.WriteLine(string.Format("col = {0}", string.Join(",", col.Select(x => x.ToString()))));

            Console.ReadKey();
        }

        private static void Col_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            var sb = new StringBuilder();


            sb.AppendLine("↓↓↓NotifyCollectionChangedEventArgsの中身↓↓↓");
            sb.AppendLine(string.Format("e.Action = {0}",e.Action.ToString()));

            if(e.NewItems == null)
            {
                sb.AppendLine("e.NewItems == null");
            }
            if(e.NewItems != null)
            {
                sb.AppendLine(string.Format("e.NewItems.Count = {0}", e.NewItems.Count.ToString()));
                sb.AppendLine(string.Format("e.NewItems = {0}", string.Join(",", e.NewItems.Cast<int>().ToArray().Select(x => x.ToString()))));
            }
            sb.AppendLine(string.Format("e.NewStartingIndex = {0}",e.NewStartingIndex.ToString()));

            if (e.OldItems == null)
            {
                sb.AppendLine("e.Oldtems == null");
            }
            if (e.OldItems != null)
            {
                sb.AppendLine(string.Format("e.OldItems.Count = {0}", e.OldItems.Count.ToString()));
                sb.AppendLine(string.Format("e.OldItems = {0}", string.Join(",", e.OldItems.Cast<int>().ToArray().Select(x => x.ToString()))));
            }
            

            sb.AppendLine(string.Format("e.OldStartingIndex = {0}", e.OldStartingIndex.ToString()));

            Debug.Write(sb.ToString());
    }
    }
}

実行結果

実行すると以下の通りデバッグ出力が得られた。

var col = new ObservableCollection<int>()
==========================================
col.Add(1)
==========================================
↓↓↓NotifyCollectionChangedEventArgsの中身↓↓↓
e.Action = Add
e.NewItems.Count = 1
e.NewItems = 1
e.NewStartingIndex = 0
e.Oldtems == null
e.OldStartingIndex = -1
col = 1
==========================================
col.Add(2)
==========================================
↓↓↓NotifyCollectionChangedEventArgsの中身↓↓↓
e.Action = Add
e.NewItems.Count = 1
e.NewItems = 2
e.NewStartingIndex = 1
e.Oldtems == null
e.OldStartingIndex = -1
col = 1,2
==========================================
col.Add(3)
==========================================
↓↓↓NotifyCollectionChangedEventArgsの中身↓↓↓
e.Action = Add
e.NewItems.Count = 1
e.NewItems = 3
e.NewStartingIndex = 2
e.Oldtems == null
e.OldStartingIndex = -1
col = 1,2,3
==========================================
col.Add(4)
==========================================
↓↓↓NotifyCollectionChangedEventArgsの中身↓↓↓
e.Action = Add
e.NewItems.Count = 1
e.NewItems = 4
e.NewStartingIndex = 3
e.Oldtems == null
e.OldStartingIndex = -1
col = 1,2,3,4
==========================================
col.Remove(3)
==========================================
↓↓↓NotifyCollectionChangedEventArgsの中身↓↓↓
e.Action = Remove
e.NewItems == null
e.NewStartingIndex = -1
e.OldItems.Count = 1
e.OldItems = 3
e.OldStartingIndex = 2
col = 1,2,4
==========================================
col.RemoveAt(1)
==========================================
↓↓↓NotifyCollectionChangedEventArgsの中身↓↓↓
e.Action = Remove
e.NewItems == null
e.NewStartingIndex = -1
e.OldItems.Count = 1
e.OldItems = 2
e.OldStartingIndex = 1
col = 1,4
==========================================
col.Move(0,1)
==========================================
↓↓↓NotifyCollectionChangedEventArgsの中身↓↓↓
e.Action = Move
e.NewItems.Count = 1
e.NewItems = 1
e.NewStartingIndex = 1
e.OldItems.Count = 1
e.OldItems = 1
e.OldStartingIndex = 0
col = 4,1
==========================================
col.Insert(1,5)
==========================================
↓↓↓NotifyCollectionChangedEventArgsの中身↓↓↓
e.Action = Add
e.NewItems.Count = 1
e.NewItems = 5
e.NewStartingIndex = 1
e.Oldtems == null
e.OldStartingIndex = -1
col = 4,5,1
==========================================
col.OrderBy(x => x)
==========================================
※異なる参照が返るのでイベントは発生しない。
col = 4,5,1
==========================================
col.Reverse()
==========================================
※異なる参照が返るのでイベントは発生しない。
col = 4,5,1
==========================================
col.Clear()
==========================================
↓↓↓NotifyCollectionChangedEventArgsの中身↓↓↓
e.Action = Reset
e.NewItems == null
e.NewStartingIndex = -1
e.Oldtems == null
e.OldStartingIndex = -1
col = 

イベント発生時に通知されるNotifyCollectionChangedEventArgsの各メンバーについてメモ

Action

・操作の内容によって値が変わる。
・Add→Add
・Insert→Add
・Move→Move
・Remove→Remove
・RemoveAt→Remove
・Clear→Reset
・SetItem→Replace(?) ※SetItemはprotected、今回は確認していない。

NewItems

・Addメソッド、Insertメソッド、Moveメソッドで対象要素のインスタンスが格納される。
・これ以外のメソッドはnullとなる。
・たぶんSetItemもここに入るだろう。

NewStartingIndex

・NewItemsの開始位置を示す
・NewItemsにインスタンスが格納されていない場合-1を取る

OldItems

・Removeメソッド、RemoveAtメソッド、Moveメソッドで削除、移動前の要素のインスタンスが格納される
・これ以外のメソッドはnullとなる
・SetItemもはいる??

OldStartingIndex

・OldItemsの開始位置を示す。
・つまり「削除・移動前に要素が入っていた位置」
・OldItemsにインスタンスが格納されていない場合-1となる

Clearメソッドについて補足

・ActionメンバーがResetを取る。
・NewItemとOldItemはnull
・NewStartingIndexとOldStartingIndexはともに-1

OrderByメソッドやReverseメソッドについて補足

・異なる参照が返るので元のコレクションの内容は変わらない
・したがってイベントも発生しない