开发者

How to create a custom observable collection using ConcurrentDictionary, INotifyCollectionChanged, INotifyPropertyChanged

I am trying to create an ObservableConcurrentDictionary. This object will be used in a multithreaded application, and it's data is used to populate a control via the controls ItemsSource property.

This is the implementation i have come up with:

public sealed class ObservableConcurrentDictionary<TKey, TValue> : ConcurrentDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
    #region Constructors

    public ObservableConcurrentDictionary()
        : base()
    { 

    }

    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
        : base(collection)
    { 

    }


    public ObservableConcurrentDictionary(IEqualityComparer<TKey> comparer)
        : base(comparer)
    { 

    }


    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity)
        : base(concurrencyLevel, capacity)
    { 

    }


    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(collection, comparer)
    { 

    }


    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, capacity, comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, collection, comparer)
    {

    }

    #endregion

    #region Public Methods

    public new TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
        {
            // Update value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace));
        }
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        }
        // Returns the value
        return value;
    }

    public void Clear()
    {
        // Clear dictionary
        base.Clear();
        // Raise event
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public new TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            value = base.GetOrAdd(key, valueFactory);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.GetOrAdd(key, valueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        }
        // Return value
        return value;
    }

    public new TValue GetOrAdd(TKey key, TValue value)
    {
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            base.GetOrAdd(key, value);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            base.GetOrAdd(key, value);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        }
        // Return value
        return value;
    }

    public new bool TryAdd(TKey key, TValue value)
    {
        // Stores tryAdd
        bool tryAdd;
        // If added
        if (tryAdd = base.TryAdd(key, value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        // Return tryAdd
        return tryAdd;
    }

    public new bool TryRemove(TKey key, out TValue value)
    {
        // Stores tryRemove
        bool tryRemove;
        // If removed
        if (tryRemove = base.TryRemove(key, out value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove));
        // Return tryAdd
        return tryRemove;
    }

    public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
    {
        // Stores tryUpdate
        bool tryUpdate;
        // If updated
        if (tryUpdate = base.TryUpdate(key, newValue, comparisonValue))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace));
        // Return tryUpdate
        return tryUpdate;
    }

    #endregion

    #region Private Methods

    private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, e);
    }

    #endregion

    #region INotifyCollectionChanged Members

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

Unfortunately the solution does not work as intended - in fact, it doesn't work at all. Any ideas on what i am doing wrong or do any better solutions exist?

Please note i CAN'T USE ObservableCollection, hence i have to write my own Observable collection.

EDIT: The working version is below. Hope this helps someone else with a similar problem.

public sealed class ObservableConcurrentDictionary<TKey, TValue> : ConcurrentDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
    public ObservableConcurrentDictionary()
        : base()
    { 

    }

    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
        : base(collection)
    { 

    }

    public ObservableConcurrentDictionary(IEqualityComparer<TKey> comparer)
        : base(comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity)
        : base(concurrencyLevel, capacity)
    { 

    }

    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(collection, comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, capacity, comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, collection, comparer)
    {

    }

    public new TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
        {
            // Update value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value));
        }
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
        }
        // Returns the value
        return value;
    }

    public new void Clear()
    {
        // Clear dictionary
        base.Clear();
        // Raise event
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public new TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            value = base.GetOrAdd(key, valueFactory);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.GetOrAdd(key, valueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
        }
        // Return value
        return value;
    }

    public new TValue GetOrAdd(TKey key, TValue value)
    {
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            base.GetOrAdd(key, value);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            base.GetOrAdd(key, value);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
    开发者_StackOverflow    }
        // Return value
        return value;
    }

    public new bool TryAdd(TKey key, TValue value)
    {
        // Stores tryAdd
        bool tryAdd;
        // If added
        if (tryAdd = base.TryAdd(key, value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
        // Return tryAdd
        return tryAdd;
    }

    public new bool TryRemove(TKey key, out TValue value)
    {
        // Stores tryRemove
        bool tryRemove;
        // If removed
        if (tryRemove = base.TryRemove(key, out value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value));
        // Return tryAdd
        return tryRemove;
    }

    public new bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
    {
        // Stores tryUpdate
        bool tryUpdate;
        // If updated
        if (tryUpdate = base.TryUpdate(key, newValue, comparisonValue))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue));
        // Return tryUpdate
        return tryUpdate;
    }

    private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, e);
    }

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    public event PropertyChangedEventHandler PropertyChanged;
}


Quickly going through your code without any eplanation from your side I can only guess. I dont think setting Action on NotifyCollectionChangedEventArgs is enough. There are also NewItems, OldItems properties, that tell subscriber what items got changed.

Also note that, while those are collections, many WPF components support only single item change at a time through DataBinding.


I developed a tight version of an ObservableConcurrentDictionnary, please comment/suggest...

... where TValue : Object {use your class instead of Object...}

Qurlet

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;

namespace Collections
{
    public class ObservableConcurrentDictionary<TValue> : ConcurrentDictionary<Int32, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
        where TValue : Object , new()
    {
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs changeAction)
        {
            var eh = CollectionChanged;
            if (eh == null) return;

            eh(this, changeAction);

            OnPropertyChanged();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged()
        {
            var eh = PropertyChanged;
            if (eh == null) return;

            // All properties : Keys, Values, Count, IsEmpty
            eh(this, new PropertyChangedEventArgs(null));
        }

        #region Ctors
        public ObservableConcurrentDictionary()
            : base()
        {

        }

        public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<Int32, TValue>> collection)
            : base(collection)
        {

        }

        public ObservableConcurrentDictionary(IEqualityComparer<Int32> comparer)
            : base(comparer)
        {

        }

        public ObservableConcurrentDictionary(int concurrencyLevel, int capacity)
            : base(concurrencyLevel, capacity)
        {

        }

        public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<Int32, TValue>> collection, IEqualityComparer<Int32> comparer)
            : base(collection, comparer)
        {

        }

        public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<Int32> comparer)
            : base(concurrencyLevel, capacity, comparer)
        {

        }

        public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<Int32, TValue>> collection, IEqualityComparer<Int32> comparer)
            : base(concurrencyLevel, collection, comparer)
        {

        }
        #endregion

        public new void Clear()
        {
            // Clear dictionary
            base.Clear();
            // Raise event
            OnCollectionChanged(changeAction: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        public new TValue AddOrUpdate(Int32 key, Func<Int32, TValue> addValueFactory,
            Func<Int32, TValue, TValue> updateValueFactory)
        {
            bool isUpdated = false;
            TValue oldValue = default(TValue);

            TValue value = base.AddOrUpdate(key, addValueFactory, (k, v) =>
            {
                isUpdated = true;
                oldValue = v;
                return updateValueFactory(k, v);
            });

            if (isUpdated) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));

            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
            return value;
        }

        public new TValue AddOrUpdate(Int32 key, TValue addValue, Func<Int32, TValue, TValue> updateValueFactory)
        {
            bool isUpdated = false;
            TValue oldValue = default(TValue);

            TValue value = base.AddOrUpdate(key, addValue, (k, v) =>
            {
                isUpdated = true;
                oldValue = v;
                return updateValueFactory(k, v);
            });

            if (isUpdated) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));

            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
            return value;
        }

        public new TValue GetOrAdd(Int32 key, Func<Int32, TValue> addValueFactory)
        {
            bool isAdded = false;

            TValue value = base.GetOrAdd(key, k =>
            {
                isAdded = true;
                return addValueFactory(k);
            });

            if (isAdded) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));

            return value;
        }

        public new TValue GetOrAdd(Int32 key, TValue value)
        {
            return GetOrAdd(key, k => value);
        }

        public new bool TryAdd(Int32 key, TValue value)
        {
            bool tryAdd = base.TryAdd(key, value);

            if (tryAdd) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));

            return tryAdd;
        }

        public new bool TryRemove(Int32 key, out TValue value)
        {
            // Stores tryRemove
            bool tryRemove = base.TryRemove(key, out value);

            // If removed raise event
            if (tryRemove) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value));

            return tryRemove;
        }

        public new bool TryUpdate(Int32 key, TValue newValue, TValue comparisonValue)
        {
            // Stores tryUpdate
            bool tryUpdate = base.TryUpdate(key, newValue, comparisonValue);

            if (tryUpdate) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue, comparisonValue));

            return tryUpdate;
        }

    }
}


For those still interested, below is a full implementation starting from the work of @MStack and @c0D3l0g1c and adding documentation, implementation of INotifyPropertyChanged, and new methods in >= .NET 4.7.

/// <summary>
/// Represents a thread-safe collection of key/value pairs that can be accessed by multiple threads concurrently and provides
/// notifications when items get added, removed, or when the whole list is refreshed.
/// </summary>
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
public sealed class ObservableConcurrentDictionary<TKey, TValue> : ConcurrentDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
   private const string IndexerName = "Item[]";

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// default concurrency level, has the default initial capacity, and uses the default comparer for the key type.
   /// </summary>
   /// <remarks>
   /// The default concurrency level is equal to the number of CPUs. The higher the concurrency level is, the more concurrent write
   /// operations can take place without interference and blocking. Higher concurrency level values also cause operations that require
   /// all locks (for example, table resizing, ToArray and Count) to become more expensive. The default capacity (DEFAULT_CAPACITY),
   /// which represents the initial number of buckets, is a trade-off between the size of a very small dictionary and the number of
   /// resizes when constructing a large dictionary. Also, the capacity should not be divisible by a small prime number. The default
   /// capacity is 31.
   /// </remarks>
   public ObservableConcurrentDictionary() : base()
   {
   }

   /// <summary>Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that contains
   /// elements copied from the specified IEnumerable<T>, has the default concurrency level, has the default initial capacity, and uses
   /// the default comparer for the key type.</summary> <param name="collection">The <see cref="IEnumerable{T}" /> whose elements are
   /// copied to the new <see cref="ObservableConcurrentDictionary{TKey, TValue}" />.</param>
   public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : base(collection)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// default concurrency level and capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(IEqualityComparer<TKey> comparer) : base(comparer)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// specified concurrency level and capacity, and uses the default comparer for the key type.
   /// </summary>
   /// <param name="concurrencyLevel">
   /// The estimated number of threads that will update the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> concurrently.
   /// </param>
   /// <param name="capacity">
   /// The initial number of elements that the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> can contain.
   /// </param>
   public ObservableConcurrentDictionary(int concurrencyLevel, int capacity) : base(concurrencyLevel, capacity)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that contains elements copied
   /// from the specified <see cref="IEnumerable{T}"/> has the default concurrency level, has the default initial capacity, and uses
   /// the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="collection">
   /// The <see cref="IEnumerable{T}"/> whose elements are copied to the new <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.
   /// </param>
   /// <param name="comparer">The <see cref="IEqualityComparer{T}"/> implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
      : base(collection, comparer)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// specified concurrency level, has the specified initial capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="concurrencyLevel">
   /// The estimated number of threads that will update the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> concurrently.
   /// </param>
   /// <param name="capacity">
   /// The initial number of elements that the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> can contain.
   /// </param>
   /// <param name="comparer">The <see cref="IEqualityComparer{T}"/> implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer)
      : base(concurrencyLevel, capacity, comparer)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that contains elements copied
   /// from the specified IEnumerable, and uses the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="concurrencyLevel">
   /// The estimated number of threads that will update the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> concurrently.
   /// </param>
   /// <param name="collection">
   /// The <see cref="IEnumerable{T}"/> whose elements are copied to the new <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.
   /// </param>
   /// <param name="comparer">The <see cref="IEqualityComparer{T}"/> implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
      : base(concurrencyLevel, collection, comparer)
   {
   }

   /// <summary>Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.</summary>
   public event NotifyCollectionChangedEventHandler CollectionChanged;

   /// <summary>Occurs when a property value changes.</summary>
   public event PropertyChangedEventHandler PropertyChanged;

   /// <summary>
   /// Uses the specified functions to add a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the
   /// key does not already exist, or to update a key/value pair in the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if
   /// the key already exists.
   /// </summary>
   /// <param name="key">The key to be added or whose value should be updated</param>
   /// <param name="addValueFactory">The function used to generate a value for an absent key</param>
   /// <param name="updateValueFactory">
   /// The function used to generate a new value for an existing key based on the key's existing value
   /// </param>
   /// <returns>
   /// The new value for the key. This will be either be the result of <paramref name="addValueFactory"/> (if the key was absent) or
   /// the result of <paramref name="updateValueFactory"/> (if the key was present).
   /// </returns>
   public new TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
   {
      var add = !ContainsKey(key);
      var value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(add ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Replace, value));
      return value;
   }

   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the key does not already exist, or
   /// updates a key/value pair in the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> by using the specified function if
   /// the key already exists.
   /// </summary>
   /// <param name="key">The key to be added or whose value should be updated</param>
   /// <param name="addValue">The value to be added for an absent key</param>
   /// <param name="updateValueFactory">
   /// The function used to generate a new value for an existing key based on the key's existing value
   /// </param>
   /// <returns>
   /// The new value for the key. This will be either be <paramref name="addValue"/> (if the key was absent) or the result of <paramref
   /// name="updateValueFactory"/> (if the key was present).
   /// </returns>
   public new TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory)
   {
      var add = !ContainsKey(key);
      var value = base.AddOrUpdate(key, addValue, updateValueFactory);
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(add ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Replace, value));
      return value;
   }

#if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462)
   /// <summary>
   /// Uses the specified functions and argument to add a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey,
   /// TValue}"/> if the key does not already exist, or to update a key/value pair in the <see
   /// cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the key already exists.
   /// </summary>
   /// <typeparam name="TArg">The type of an argument to pass into <paramref name="addValueFactory"/> and <paramref name="updateValueFactory"/>.</typeparam>
   /// <param name="key">The key to be added or whose value should be updated.</param>
   /// <param name="addValueFactory">The function used to generate a value for an absent key.</param>
   /// <param name="updateValueFactory">
   /// The function used to generate a new value for an existing key based on the key's existing value.
   /// </param>
   /// <param name="factoryArgument">An argument to pass into <paramref name="addValueFactory"/> and <paramref name="updateValueFactory"/>.</param>
   /// <returns>
   /// The new value for the key. This will be either be the result of <paramref name="addValueFactory"/> (if the key was absent) or
   /// the result of <paramref name="updateValueFactory"/> (if the key was present).
   /// </returns>
   public new TValue AddOrUpdate<TArg>(TKey key, Func<TKey, TArg, TValue> addValueFactory, Func<TKey, TValue, TArg, TValue> updateValueFactory, TArg factoryArgument)
   {
      var add = !ContainsKey(key);
      var value = base.AddOrUpdate(key, addValueFactory, updateValueFactory, factoryArgument);
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(add ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Replace, value));
      return value;
   }
#endif

   /// <summary>Removes all keys and values from the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.</summary>
   public new void Clear()
   {
      base.Clear();
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
   }

   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> by using the specified function if the
   /// key does not already exist. Returns the new value, or the existing value if the key exists.
   /// </summary>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="valueFactory">The function used to generate a value for the key.</param>
   /// <returns>
   /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new
   /// value if the key was not in the dictionary.
   /// </returns>
   public new TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
   {
      var add = !ContainsKey(key);
      var value = base.GetOrAdd(key, valueFactory);
      if (add) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
      return value;
   }

   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the key does not already exist.
   /// Returns the new value, or the existing value if the key exists.
   /// </summary>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="value">The value to be added, if the key does not already exist.</param>
   /// <returns>
   /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new
   /// value if the key was not in the dictionary.
   /// </returns>
   public new TValue GetOrAdd(TKey key, TValue value)
   {
      var add = !ContainsKey(key);
      base.GetOrAdd(key, value);
      if (add) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
      return value;
   }

#if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462)
   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> by using the specified function and an
   /// argument if the key does not already exist, or returns the existing value if the key exists.
   /// </summary>
   /// <typeparam name="TArg">The type of an argument to pass into <paramref name="valueFactory"/>.</typeparam>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="valueFactory">The function used to generate a value for the key.</param>
   /// <param name="factoryArgument">An argument value to pass into <paramref name="valueFactory"/>.</param>
   /// <returns>
   /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new
   /// value if the key was not in the dictionary.
   /// </returns>
   public new TValue GetOrAdd<TArg>(TKey key, Func<TKey, TArg, TValue> valueFactory, TArg factoryArgument)
   {
      var add = !ContainsKey(key);
      var value = base.GetOrAdd(key, valueFactory, factoryArgument);
      if (add) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
      return value;
   }
#endif

   /// <summary>Attempts to add the specified key and value to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.</summary>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="value">The value of the element to add. The value can be <see langword="null"/> for reference types.</param>
   /// <returns>
   /// <see langword="true"/> if the key/value pair was added to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>
   /// successfully; <see langword="false"/> if the key already exists.
   /// </returns>
   public new bool TryAdd(TKey key, TValue value)
   {
      if (base.TryAdd(key, value))
      {
         OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
         return true;
      }
      return false;
   }

   /// <summary>
   /// Attempts to remove and return the value that has the specified key from the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.
   /// </summary>
   /// <param name="key">The key of the element to remove and return.</param>
   /// <param name="value">
   /// When this method returns, contains the object removed from the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>, or
   /// the default value of the <typeparamref name="TValue"/> type if <paramref name="key"/> does not exist.
   /// </param>
   /// <returns><see langword="true"/> if the object was removed successfully; otherwise, <see langword="false"/>.</returns>
   public new bool TryRemove(TKey key, out TValue value)
   {
      if (base.TryRemove(key, out value))
      {
         OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value));
         return true;
      }
      return false;
   }

   /// <summary>
   /// Updates the value associated with <paramref name="key"/> to <paramref name="newValue"/> if the existing value with <paramref
   /// name="key"/> is equal to <paramref name="comparisonValue"/>.
   /// </summary>
   /// <param name="key">The key of the value that is compared with <paramref name="comparisonValue"/> and possibly replaced.</param>
   /// <param name="newValue">
   /// The value that replaces the value of the element that has the specified <paramref name="key"/> if the comparison results in equality.
   /// </param>
   /// <param name="comparisonValue">The value that is compared with the value of the element that has the specified <paramref name="key"/>.</param>
   /// <returns>
   /// <see langword="true"/> if the value with <paramref name="key"/> was equal to <paramref name="comparisonValue"/> and was replaced
   /// with <paramref name="newValue"/>; otherwise, <see langword="false"/>.
   /// </returns>
   public new bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
   {
      if (base.TryUpdate(key, newValue, comparisonValue))
      {
         OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue));
         return true;
      }
      return false;
   }

   /// <summary>Raises the <see cref="CollectionChanged"/> event.</summary>
   /// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
   private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   {
      if (e.Action != NotifyCollectionChangedAction.Replace) OnPropertyChanged(nameof(Count));
      OnPropertyChanged(IndexerName);
      CollectionChanged?.Invoke(this, e);
   }

   /// <summary>Raises the <see cref="PropertyChanged" /> event.</summary>
   /// <param name="propertyName">Name of the property that has changed.</param>
   private void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜