File: System\Collections\Specialized\NotifyCollectionChangedEventArgs.cs
Web Access
Project: src\src\libraries\System.ObjectModel\src\System.ObjectModel.csproj (System.ObjectModel)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
 
namespace System.Collections.Specialized
{
    /// <summary>
    /// Arguments for the CollectionChanged event.
    /// A collection that supports INotifyCollectionChangedThis raises this event
    /// whenever an item is added or removed, or when the contents of the collection
    /// changes dramatically.
    /// </summary>
    public class NotifyCollectionChangedEventArgs : EventArgs
    {
        private readonly NotifyCollectionChangedAction _action;
        private readonly IList? _newItems;
        private readonly IList? _oldItems;
        private readonly int _newStartingIndex = -1;
        private readonly int _oldStartingIndex = -1;
 
        /// <summary>
        /// Construct a NotifyCollectionChangedEventArgs that describes a reset change.
        /// </summary>
        /// <param name="action">The action that caused the event (must be Reset).</param>
        public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action)
        {
            if (action != NotifyCollectionChangedAction.Reset)
            {
                throw new ArgumentException(SR.Format(SR.WrongActionForCtor, NotifyCollectionChangedAction.Reset), nameof(action));
            }
 
            _action = action;
        }
 
        /// <summary>
        /// Construct a NotifyCollectionChangedEventArgs that describes a one-item change.
        /// </summary>
        /// <param name="action">The action that caused the event; can only be Reset, Add or Remove action.</param>
        /// <param name="changedItem">The item affected by the change.</param>
        public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object? changedItem) :
            this(action, changedItem, -1)
        {
        }
 
        /// <summary>
        /// Construct a NotifyCollectionChangedEventArgs that describes a one-item change.
        /// </summary>
        /// <param name="action">The action that caused the event.</param>
        /// <param name="changedItem">The item affected by the change.</param>
        /// <param name="index">The index where the change occurred.</param>
        public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object? changedItem, int index)
        {
            switch (action)
            {
                case NotifyCollectionChangedAction.Reset:
                    if (changedItem != null)
                    {
                        throw new ArgumentException(SR.ResetActionRequiresNullItem, nameof(action));
                    }
                    if (index != -1)
                    {
                        throw new ArgumentException(SR.ResetActionRequiresIndexMinus1, nameof(action));
                    }
                    break;
 
                case NotifyCollectionChangedAction.Add:
                    _newItems = new SingleItemReadOnlyList(changedItem);
                    _newStartingIndex = index;
                    break;
 
                case NotifyCollectionChangedAction.Remove:
                    _oldItems = new SingleItemReadOnlyList(changedItem);
                    _oldStartingIndex = index;
                    break;
 
                default:
                    throw new ArgumentException(SR.MustBeResetAddOrRemoveActionForCtor, nameof(action));
            }
 
            _action = action;
        }
 
        /// <summary>
        /// Construct a NotifyCollectionChangedEventArgs that describes a multi-item change.
        /// </summary>
        /// <param name="action">The action that caused the event.</param>
        /// <param name="changedItems">The items affected by the change.</param>
        public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList? changedItems) :
            this(action, changedItems, -1)
        {
        }
 
        /// <summary>
        /// Construct a NotifyCollectionChangedEventArgs that describes a multi-item change (or a reset).
        /// </summary>
        /// <param name="action">The action that caused the event.</param>
        /// <param name="changedItems">The items affected by the change.</param>
        /// <param name="startingIndex">The index where the change occurred.</param>
        public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList? changedItems, int startingIndex)
        {
            switch (action)
            {
                case NotifyCollectionChangedAction.Reset:
                    if (changedItems != null)
                    {
                        throw new ArgumentException(SR.ResetActionRequiresNullItem, nameof(action));
                    }
                    if (startingIndex != -1)
                    {
                        throw new ArgumentException(SR.ResetActionRequiresIndexMinus1, nameof(action));
                    }
                    break;
 
                case NotifyCollectionChangedAction.Add:
                case NotifyCollectionChangedAction.Remove:
                    ArgumentNullException.ThrowIfNull(changedItems);
                    ArgumentOutOfRangeException.ThrowIfLessThan(startingIndex, -1);
 
                    if (action == NotifyCollectionChangedAction.Add)
                    {
                        _newItems = new ReadOnlyList(changedItems);
                        _newStartingIndex = startingIndex;
                    }
                    else
                    {
                        _oldItems = new ReadOnlyList(changedItems);
                        _oldStartingIndex = startingIndex;
                    }
                    break;
 
                default:
                    throw new ArgumentException(SR.MustBeResetAddOrRemoveActionForCtor, nameof(action));
            }
 
            _action = action;
        }
 
        /// <summary>
        /// Construct a NotifyCollectionChangedEventArgs that describes a one-item Replace event.
        /// </summary>
        /// <param name="action">Can only be a Replace action.</param>
        /// <param name="newItem">The new item replacing the original item.</param>
        /// <param name="oldItem">The original item that is replaced.</param>
        public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object? newItem, object? oldItem) :
            this(action, newItem, oldItem, -1)
        {
        }
 
        /// <summary>
        /// Construct a NotifyCollectionChangedEventArgs that describes a one-item Replace event.
        /// </summary>
        /// <param name="action">Can only be a Replace action.</param>
        /// <param name="newItem">The new item replacing the original item.</param>
        /// <param name="oldItem">The original item that is replaced.</param>
        /// <param name="index">The index of the item being replaced.</param>
        public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object? newItem, object? oldItem, int index)
        {
            if (action != NotifyCollectionChangedAction.Replace)
            {
                throw new ArgumentException(SR.Format(SR.WrongActionForCtor, NotifyCollectionChangedAction.Replace), nameof(action));
            }
 
            _action = action;
            _newItems = new SingleItemReadOnlyList(newItem);
            _oldItems = new SingleItemReadOnlyList(oldItem);
            _newStartingIndex = _oldStartingIndex = index;
        }
 
        /// <summary>
        /// Construct a NotifyCollectionChangedEventArgs that describes a multi-item Replace event.
        /// </summary>
        /// <param name="action">Can only be a Replace action.</param>
        /// <param name="newItems">The new items replacing the original items.</param>
        /// <param name="oldItems">The original items that are replaced.</param>
        public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList newItems, IList oldItems) :
            this(action, newItems, oldItems, -1)
        {
        }
 
        /// <summary>
        /// Construct a NotifyCollectionChangedEventArgs that describes a multi-item Replace event.
        /// </summary>
        /// <param name="action">Can only be a Replace action.</param>
        /// <param name="newItems">The new items replacing the original items.</param>
        /// <param name="oldItems">The original items that are replaced.</param>
        /// <param name="startingIndex">The starting index of the items being replaced.</param>
        public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList newItems, IList oldItems, int startingIndex)
        {
            if (action != NotifyCollectionChangedAction.Replace)
            {
                throw new ArgumentException(SR.Format(SR.WrongActionForCtor, NotifyCollectionChangedAction.Replace), nameof(action));
            }
            ArgumentNullException.ThrowIfNull(newItems);
            ArgumentNullException.ThrowIfNull(oldItems);
 
            _action = action;
            _newItems = new ReadOnlyList(newItems);
            _oldItems = new ReadOnlyList(oldItems);
            _newStartingIndex = _oldStartingIndex = startingIndex;
        }
 
        /// <summary>
        /// Construct a NotifyCollectionChangedEventArgs that describes a one-item Move event.
        /// </summary>
        /// <param name="action">Can only be a Move action.</param>
        /// <param name="changedItem">The item affected by the change.</param>
        /// <param name="index">The new index for the changed item.</param>
        /// <param name="oldIndex">The old index for the changed item.</param>
        public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object? changedItem, int index, int oldIndex)
        {
            if (action != NotifyCollectionChangedAction.Move)
            {
                throw new ArgumentException(SR.Format(SR.WrongActionForCtor, NotifyCollectionChangedAction.Move), nameof(action));
            }
            ArgumentOutOfRangeException.ThrowIfNegative(index);
 
            _action = action;
            _newItems = _oldItems = new SingleItemReadOnlyList(changedItem);
            _newStartingIndex = index;
            _oldStartingIndex = oldIndex;
        }
 
        /// <summary>
        /// Construct a NotifyCollectionChangedEventArgs that describes a multi-item Move event.
        /// </summary>
        /// <param name="action">The action that caused the event.</param>
        /// <param name="changedItems">The items affected by the change.</param>
        /// <param name="index">The new index for the changed items.</param>
        /// <param name="oldIndex">The old index for the changed items.</param>
        public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList? changedItems, int index, int oldIndex)
        {
            if (action != NotifyCollectionChangedAction.Move)
            {
                throw new ArgumentException(SR.Format(SR.WrongActionForCtor, NotifyCollectionChangedAction.Move), nameof(action));
            }
            ArgumentOutOfRangeException.ThrowIfNegative(index);
 
            _action = action;
            _newItems = _oldItems = changedItems is not null ? new ReadOnlyList(changedItems) : null;
            _newStartingIndex = index;
            _oldStartingIndex = oldIndex;
        }
 
        /// <summary>
        /// The action that caused the event.
        /// </summary>
        public NotifyCollectionChangedAction Action => _action;
 
        /// <summary>
        /// The items affected by the change.
        /// </summary>
        public IList? NewItems => _newItems;
 
        /// <summary>
        /// The old items affected by the change (for Replace events).
        /// </summary>
        public IList? OldItems => _oldItems;
 
        /// <summary>
        /// The index where the change occurred.
        /// </summary>
        public int NewStartingIndex => _newStartingIndex;
 
        /// <summary>
        /// The old index where the change occurred (for Move events).
        /// </summary>
        public int OldStartingIndex => _oldStartingIndex;
    }
 
    /// <summary>
    /// The delegate to use for handlers that receive the CollectionChanged event.
    /// </summary>
    public delegate void NotifyCollectionChangedEventHandler(object? sender, NotifyCollectionChangedEventArgs e);
 
    internal sealed class ReadOnlyList : IList
    {
        private readonly IList _list;
 
        internal ReadOnlyList(IList list)
        {
            Debug.Assert(list != null);
            _list = list;
        }
 
        public int Count => _list.Count;
 
        public bool IsReadOnly => true;
 
        public bool IsFixedSize => true;
 
        public bool IsSynchronized => _list.IsSynchronized;
 
        public object? this[int index]
        {
            get => _list[index];
            set => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
        }
 
        public object SyncRoot => _list.SyncRoot;
 
        public int Add(object? value) => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        public void Clear() => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        public bool Contains(object? value) => _list.Contains(value);
 
        public void CopyTo(Array array, int index) => _list.CopyTo(array, index);
 
        public IEnumerator GetEnumerator() => _list.GetEnumerator();
 
        public int IndexOf(object? value) => _list.IndexOf(value);
 
        public void Insert(int index, object? value) => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        public void Remove(object? value) => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        public void RemoveAt(int index) => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
    }
 
    internal sealed class SingleItemReadOnlyList : IList
    {
        private readonly object? _item;
 
        public SingleItemReadOnlyList(object? item) => _item = item;
 
        public object? this[int index]
        {
            get
            {
                ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0);
                return _item;
            }
            set => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
        }
 
        public bool IsFixedSize => true;
 
        public bool IsReadOnly => true;
 
        public int Count => 1;
 
        public bool IsSynchronized => false;
 
        public object SyncRoot => this;
 
        public IEnumerator GetEnumerator()
        {
            yield return _item;
        }
 
        public bool Contains(object? value) => _item is null ? value is null : _item.Equals(value);
 
        public int IndexOf(object? value) => Contains(value) ? 0 : -1;
 
        public void CopyTo(Array array, int index)
        {
            CollectionHelpers.ValidateCopyToArguments(1, array, index);
            array.SetValue(_item, index);
        }
 
        public int Add(object? value) => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
        public void Clear() => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
        public void Insert(int index, object? value) => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
        public void Remove(object? value) => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
        public void RemoveAt(int index) => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
    }
}