Наблюдаемый стек и очередь

Я ищу INotifyCollectionChanged реализацию Stack и Queue. Я мог бы свернуть свой собственный, но я не хочу изобретать велосипед.


person Goran    schedule 27.06.2010    source источник


Ответы (4)


Со стеками и очередями (почти по определению) у вас есть доступ только к вершине стека или голове очереди. Это то, что отличает их от List. (Итак, поэтому вы не нашли его)

Чтобы ответить, хотя вы могли бы написать свой собственный, я бы сделал это, наследуя от ObservableCollection, а затем в случае стека, реализующего Push как Insert со смещением 0 (и всплывающее окно как возвращающий индекс 0, затем RemoveAt индекс 0); или с очередью вы могли бы просто Add в конец списка до Enqueue, а захватить и удалить первый элемент, как и со стеком, за Dequeue. Операции Insert, Add и RemoveAt будут вызываться для базового ObservableCollection и вызывать событие CollectionChanged.


Вы также можете сказать, что просто хотите привязать или получать уведомления, когда один элемент, к которому у вас должен быть доступ, изменяется. Вы должны снова создать свой собственный класс, производный от Stack или Queue, и запустить событие CollectionChanged вручную, когда:

  • Что-то помещается в стек или выталкивается из него
  • Что-то удалено из очереди
  • Что-то поставлено в очередь в очереди, когда ранее очередь была пустой
person Kieren Johnstone    schedule 27.06.2010
comment
Я рекомендую первый подход для ObservableStack - получить из (или лучше содержать) ObservableCollection. Второй подход был бы лучше для ObservableQueue - получить от Queue и реализовать свои собственные уведомления. Это связано с тем, что любой ObservableQueue, построенный на List, будет иметь производительность O(N) для Enqueue или Dequeue, тогда как все остальное будет O(1). Это может повлиять на производительность, если в очереди много элементов. - person Stephen Cleary; 27.06.2010
comment
Я решил создать общий наблюдаемый класс, который просто реализует INotifyCollectionChanged. Классы вызывают внутренние методы Stack и Queue и вызывают соответствующее событие. Предпочтение композиции наследованию, поскольку методы Stack и Queue не являются виртуальными (я не понимаю, почему). - person Goran; 27.06.2010

Я столкнулся с той же проблемой и хочу поделиться своим решением с другими. Надеюсь, это кому-то поможет.

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; }
    }
}
person 0xbadf00d    schedule 05.06.2011
comment
Привет. При возникновении ошибки после события Pop() Collection Remove необходимо указать позицию элемента. все равно это исправить? спасибо - person Jayson Ragasa; 24.11.2012
comment
base.Count, поскольку позиция отсутствующего элемента исправила это для меня. public new virtual T Pop() { var item = base.Pop(); this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, base.Count)); вернуть изделие; } - person uli78; 15.01.2013
comment
Я предпочитаю это решение принятому ответу. Он позволяет поддерживать ожидаемую производительность и семантику стека/очереди, а не просто моделировать их с помощью списка (который, например, дорого удалять с самого начала по сравнению с очередью). - person KChaloux; 24.04.2014
comment
Это действительно полезно для кого-то .. как я .. :) +1 - person Christian Mark; 15.06.2015
comment
@uli78: base.Count? Разве это не должно быть 0, поскольку измененный элемент в случае Pop (из стека) всегда будет первым элементом? - person dotNET; 21.09.2016
comment
@uli78: И base.Count, и 0 вызывают у меня исключение. :( - person dotNET; 22.09.2016
comment
Я тоже получаю исключение с base.Count, но только иногда/случайно... - person Metal450; 17.10.2020

Я понимаю, что уже есть несколько ответов, но решил немного отплатить своим. Я собрал все, что упоминалось в сообщениях и комментариях. Было несколько вещей, которые побудили меня сделать это:

  • INPC всегда должен срабатывать для Count при вызове Push, Pop или Clear, как упоминалось в одном из сообщений.
  • Для Clear действие должно быть Reset, а для индекса события изменения коллекции должно быть установлено значение -1 (которое по умолчанию все равно будет установлено, если оно не установлено, чтобы оно было в других сообщениях): документы .NET
  • Для Push/Pop действие должно быть Add/Remove, а индекс для события изменения коллекции должен быть 0 для стека, который всегда и только первый элемент, которым можно манипулировать (думаю, stack.GetEnumerator().MoveNext()).
  • Открыты все 3 конструктора, доступные в Stack<T>, и используются вызовы 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
}
person Ernie S    schedule 16.05.2019

Очень похож на класс выше, за некоторыми исключениями:

  1. Опубликовать реквизит изменен для изменений коллекции для Count
  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;
}

    }
person outbred    schedule 02.02.2015
comment
Я добавил локальную реализацию вместо CLRExtensions. Я надеюсь, что это не слишком из ряда вон выходящее. - person VoteCoffee; 18.03.2018