Observable Stack and Queue
Asked Answered
L

4

19

I'm looking for an INotifyCollectionChanged implementation of Stack and Queue. I could roll my own but I don't want to reinvent the wheel.

Larkin answered 27/6, 2010 at 10:56 Comment(0)
R
11

With Stacks and Queues (almost by definition) you only have access to the top of the stack or head of the queue. It's what differentiates them from a List. (and so, that's why you haven't found one)

To answer though you could write your own, I would do it by deriving from ObservableCollection, then in the case of a stack implementing the Push as an Insert at offset 0 (and pop as returning index 0 then RemoveAt index 0); or with a queue you could just Add to the end of the list to Enqueue, and the grab and remove the first item, as with the stack, for Dequeue. The Insert, Add and RemoveAt operations would be called on the underlying ObservableCollection and so cause the CollectionChanged event to be fired.


You might also be saying that you simply want to bind or be notified when the one item you are supposed to have access to changes. You would create your own class again, derived from Stack or Queue, and fire the CollectionChanged event manually when:

  • Something is pushed onto or popped from a stack
  • Something is dequeued from a queue
  • Something is queued on the queue, when the queue was previously empty
Remiss answered 27/6, 2010 at 11:15 Comment(2)
I recommend the first approach for ObservableStack - derive from (or better, contain) an ObservableCollection. The second approach would be better for ObservableQueue - derive from Queue and implement your own notifications. This is because any ObservableQueue built on a List will have O(N) performance for either Enqueue or Dequeue, whereas everything else will be O(1). This would have a performance impact if there are a lot of elements in the queue.Dichloride
I decided to make a generic observable class that simply implement INotifyCollectionChanged. Classes call internal Stack and Queue methods and raise the appropriate event. Favoring composition over inheritance as Stack and Queue methods aren't virtual (which I'm having trouble understanding why).Larkin
A
34

I run into the same issue and want to share my solution to others. Hope this is helpful to someone.

public class ObservableStack<T> : Stack<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
    public ObservableStack()
    {
    }

    public ObservableStack(IEnumerable<T> collection)
    {
        foreach (var item in collection)
            base.Push(item);
    }

    public ObservableStack(List<T> list)
    {
        foreach (var item in list)
            base.Push(item);
    }


    public new virtual void Clear()
    {
        base.Clear();
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public new virtual T Pop()
    {
        var item = base.Pop();
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
        return item;
    }

    public new virtual void Push(T item)
    {
        base.Push(item);
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
    }


    public virtual event NotifyCollectionChangedEventHandler CollectionChanged;


    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        this.RaiseCollectionChanged(e);
    }

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        this.RaisePropertyChanged(e);
    }


    protected virtual event PropertyChangedEventHandler PropertyChanged;


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

    private void RaisePropertyChanged(PropertyChangedEventArgs e)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, e);
    }


    event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
    {
        add { this.PropertyChanged += value; }
        remove { this.PropertyChanged -= value; }
    }
}
Anhedral answered 5/6, 2011 at 19:15 Comment(6)
Hi. Having an error after Pop() "Collection Remove event must specify item position." anyway to fix this? tnxRohr
base.Count as the missing item position fixed it for me. public new virtual T Pop() { var item = base.Pop(); this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, base.Count)); return item; }Waylonwayman
I prefer this solution to the accepted answer. It lets you maintain the performance expectations and semantics of a Stack/Queue, rather than just simulating it with a list (which, for example, is expensive to remove from the start of, compared to a Queue).Boethius
@uli78: base.Count? Shouldn't it be 0 since the changed item in the case of Pop (of Stack) would always be the first item?Handcuff
@uli78: Both base.Count and 0 throw exception for me. :(Handcuff
I'm getting an exception with base.Count too - but only sometimes/at random...Senatorial
R
11

With Stacks and Queues (almost by definition) you only have access to the top of the stack or head of the queue. It's what differentiates them from a List. (and so, that's why you haven't found one)

To answer though you could write your own, I would do it by deriving from ObservableCollection, then in the case of a stack implementing the Push as an Insert at offset 0 (and pop as returning index 0 then RemoveAt index 0); or with a queue you could just Add to the end of the list to Enqueue, and the grab and remove the first item, as with the stack, for Dequeue. The Insert, Add and RemoveAt operations would be called on the underlying ObservableCollection and so cause the CollectionChanged event to be fired.


You might also be saying that you simply want to bind or be notified when the one item you are supposed to have access to changes. You would create your own class again, derived from Stack or Queue, and fire the CollectionChanged event manually when:

  • Something is pushed onto or popped from a stack
  • Something is dequeued from a queue
  • Something is queued on the queue, when the queue was previously empty
Remiss answered 27/6, 2010 at 11:15 Comment(2)
I recommend the first approach for ObservableStack - derive from (or better, contain) an ObservableCollection. The second approach would be better for ObservableQueue - derive from Queue and implement your own notifications. This is because any ObservableQueue built on a List will have O(N) performance for either Enqueue or Dequeue, whereas everything else will be O(1). This would have a performance impact if there are a lot of elements in the queue.Dichloride
I decided to make a generic observable class that simply implement INotifyCollectionChanged. Classes call internal Stack and Queue methods and raise the appropriate event. Favoring composition over inheritance as Stack and Queue methods aren't virtual (which I'm having trouble understanding why).Larkin
S
6

I realize there are already a few answers but figured I would give back a little with mine. I put together everything mentioned in the posts and comments. There were few things that motivated me to do this:

  • INPC should always fire for Count when Push, Pop, or Clear are called, as mentioned in one of the posts.
  • For Clear, action should be Reset and index for the collection change event should be set to -1 (which it will default to anyway if not set so the other posts have that): .NET docs
  • For Push/Pop, action should be Add/Remove and index for the collection changed event should be 0 for a stack being that it is always and only the first item that can be maniuplated (think stack.GetEnumerator().MoveNext()).
  • Exposed all 3 constructors available in Stack<T> and use base() calls since there is no reason to override the logic.

Results in:

public class ObservableStack<T> : Stack<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
    #region Constructors
    
    public ObservableStack() : base() { }

    public ObservableStack(IEnumerable<T> collection) : base(collection) { }

    public ObservableStack(int capacity) : base(capacity) { }

    #endregion
    
    #region Overrides

    public virtual new T Pop()
    {
        var item = base.Pop();
        OnCollectionChanged(NotifyCollectionChangedAction.Remove, item);

        return item;
    }

    public virtual new void Push(T item)
    {
        base.Push(item);
        OnCollectionChanged(NotifyCollectionChangedAction.Add, item);
    }

    public virtual new void Clear()
    {
        base.Clear();
        OnCollectionChanged(NotifyCollectionChangedAction.Reset, default);
    }

    #endregion

    #region CollectionChanged

    public virtual event NotifyCollectionChangedEventHandler CollectionChanged;

    protected virtual void OnCollectionChanged(NotifyCollectionChangedAction action, T item)
    {
        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(
            action
            , item
            , item == null ? -1 : 0)
        );

        OnPropertyChanged(nameof(Count));
    }

    #endregion

    #region PropertyChanged

    public virtual event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}
Supplemental answered 16/5, 2019 at 23:46 Comment(0)
P
0

Very similar to the above class, with a few exceptions:

  1. Publish prop changed for collection changes for Count
  2. Override TrimExcess() b/c that could affect Count
  3. Made events public so I don't have to cast to the interface
  4. Passes index to collectionchanged when appropriate
    public class ObservableStack : Stack, INotifyPropertyChanged, INotifyCollectionChanged
    {
      public ObservableStack(IEnumerable collection) : base(collection) {}
      public ObservableStack() { } 

      public event PropertyChangedEventHandler PropertyChanged = delegate { };
      public event NotifyCollectionChangedEventHandler CollectionChanged = delegate { };

      protected virtual void OnCollectionChanged(NotifyCollectionChangedAction action, List items, int? index = null)
      {
        if (index.HasValue)
        {
            CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, items, index.Value));
        }
        else
        {
            CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, items));
        }
         OnPropertyChanged(GetPropertyName(() => Count));
      }

      protected virtual void OnPropertyChanged(string propName)
      {
        PropertyChanged(this, new PropertyChangedEventArgs(propName));
      }

      public new virtual void Clear()
      {
        base.Clear();
        OnCollectionChanged(NotifyCollectionChangedAction.Reset, null);
      }

      public new virtual T Pop()
      {
        var result = base.Pop();
        OnCollectionChanged(NotifyCollectionChangedAction.Remove, new List() { result }, base.Count);
        return result;
      }

      public new virtual void Push(T item)
      {
        base.Push(item);
        OnCollectionChanged(NotifyCollectionChangedAction.Add, new List() { item }, base.Count - 1);
      }   

      public new virtual void TrimExcess()
      {
        base.TrimExcess();
        OnPropertyChanged(GetPropertyName(() => Count));
      }

String GetPropertyName(Expression> propertyId)
{
   return ((MemberExpression)propertyId.Body).Member.Name;
}

    }
Prepare answered 2/2, 2015 at 15:31 Comment(2)
I added a local implementation in place of CLRExtensions. I hope that's not too out of line.Telegony
"the above class" don't use words like "above" when referring to other answers. people can change sort orders, and scores change over time. link to the answer you are referring to.Walt

© 2022 - 2024 — McMap. All rights reserved.