可观察的栈和队列

20

我正在寻找一个实现了INotifyCollectionChanged接口的StackQueue。虽然我可以自己实现,但我不想重复造轮子。

4个回答

34

我遇到了同样的问题,想把我的解决方案分享给其他人。希望对某些人有所帮助。

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; }
    }
}

3
您好。在使用Pop()方法后遇到了一个错误:“集合删除事件必须指定项目位置。”有什么办法可以解决吗?谢谢。 - Jayson Ragasa
3
base.Count作为缺失项的位置修复了我的问题。 public new virtual T Pop() { var item = base.Pop(); this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, base.Count)); return item; } - uli78
我更喜欢这个解决方案而不是被接受的答案。它可以让你保持Stack/Queue的性能期望和语义,而不仅仅是用列表模拟它(例如,与队列相比,从列表开头删除元素是很昂贵的)。 - KChaloux
1
这对像我这样的人确实很有帮助..:) +1 - Mark
@uli78:对我来说,base.Count0都会抛出异常。 :( - dotNET
显示剩余2条评论

10
使用堆栈和队列(几乎定义如此),你只能访问堆栈顶部或队列头部。这就是它们与List的区别(因此,您没有找到一个)。如果您想要自己编写,可以从ObservableCollection派生,然后在堆栈的情况下将Push实现为在偏移0处插入(并且弹出返回索引0,然后RemoveAt索引0);或者对于队列,您可以只将元素Add到列表末尾以Enqueue,然后像堆栈那样获取和删除第一个元素来进行DequeueInsertAddRemoveAt操作将在底层的ObservableCollection上调用,从而导致触发CollectionChanged事件。
您可能还希望绑定或在您应该访问的一个项目更改时得到通知。您需要再次创建自己的类,从堆栈或队列派生,当以下情况之一发生时手动触发CollectionChanged事件:
  • 有东西被压入或从堆栈弹出
  • 从队列中取出了某些东西
  • 在队列之前为空时,有东西被排队

5
我建议对于 ObservableStack 采用第一种方法 - 继承(或更好的是包含)一个 ObservableCollection。对于 ObservableQueue ,采用第二种方法会更好 - 继承 Queue 并实现自己的通知。因为基于 List 的任何 ObservableQueue 在执行 EnqueueDequeue 操作时都将有 O(N) 的性能,而其他操作都是 O(1)。如果队列中有大量元素,则这将对性能产生影响。 - Stephen Cleary
我决定创建一个通用的可观察类,该类只是实现了INotifyCollectionChanged接口。其他类调用内部栈和队列方法并触发相应的事件。我倾向于使用组合而非继承,因为栈和队列方法不是虚拟的(我对此的原因有些难以理解)。 - Goran

7
我知道已经有几个答案了,但我想用我的回答回报一些。我总结了帖子和评论中提到的所有内容,并有几件事情激励我这样做:
  • 当调用PushPopClear时,Count应该始终触发INPC,正如帖子中提到的那样。
  • 对于Clear,操作应为Reset,集合更改事件的索引应设置为-1(如果未设置,它将默认为-1,所以其他帖子也有这个):.NET文档
  • 对于Push/Pop,操作应为Add/Remove,堆栈的集合更改事件的索引应为0,因为只能操作第一个元素(可以考虑使用stack.GetEnumerator().MoveNext())。
  • 公开了Stack<T>中提供的所有3个构造函数,并使用base()调用,因为没有理由重写逻辑。

结果为:

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 proertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(proertyName));
    }

    #endregion
}

1
非常类似于上面的类,但有几个例外:
  1. 发布属性更改以进行计数集合更改
  2. 覆盖TrimExcess() b/c可能会影响Count
  3. 将事件设为公共,这样我就不必转换为接口
  4. 在适当时将索引传递给collectionchanged
    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;
}

    }

1
我在 CLRExtensions 的位置上添加了一个本地实现。希望这不会太过分。 - VoteCoffee
请不要使用"上面"之类的词汇来提及其他回答。因为人们可以更改排序方式,得分也会随时间变化而变化。请提供您所参考的答案链接。"the above class" - starball

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接