Расчет изменяющейся коллекции в реальном времени (в С#)

У меня есть изменяющаяся коллекция изменяющихся объектов (также возможны добавление/удаление в коллекции, а также изменение свойств).

Я хочу получить новую коллекцию с помощью некоторого вычисления в этой коллекции (суммирование/умножение по некоторым полям всех объектов, имеющих одинаковое значение поля). И привяжите рассчитанный список к пользовательскому интерфейсу.

например: Итак, у меня есть список ставок и количества различных качеств нескольких фруктов, и теперь я хочу найти новый список со средней скоростью и общим количеством каждого фрукта (независимо от качества), учитывая, что скорость и количество могут быть изменены в время работы, а также некоторые другие качества и виды фруктов могут быть добавлены к коллекции.

введите здесь описание изображения

Теперь, если ставки или количество изменяются во время выполнения в первом списке, изменения также должны отражаться во втором списке. введите здесь описание изображения

Пожалуйста, предложите, какие методы следует использовать для достижения этого, учитывая, что мы привязываем список к Datagrid.


person veerendra gupta    schedule 15.07.2014    source источник
comment
LINQ должен быть вашим другом - методы GroupBy, Sum и Average.   -  person Konrad Kokosa    schedule 15.07.2014
comment
Вы уже пробовали что-нибудь? Какой-нибудь код, который мы могли видеть?   -  person Kaizen Programmer    schedule 15.07.2014
comment
Было бы мудрой идеей выполнять все расчеты снова и снова при каждом изменении? Это приведет к снижению производительности. В моем случае у меня есть 100-400 элементов в первом списке и еще 4 свойства, которые меняются. Итак, если я выполняю этот расчет для каждого изменения, то средняя частота расчета составляет 40-50.   -  person veerendra gupta    schedule 15.07.2014
comment
@ micheal: groupBy(Name) и затем агрегация по всем этим группам, но это слишком медленно.   -  person veerendra gupta    schedule 15.07.2014
comment
Я подписываюсь на notifypropertychanged и calculatechange и каждый раз запускаю запрос linq. Можно ли как-то контролировать это время расчета - скажем, запускать запрос linq после каждых 50 мс и, если возможно, в фоновом режиме, чтобы пользовательский интерфейс не зависал.   -  person veerendra gupta    schedule 15.07.2014


Ответы (1)


Существует аккуратное реактивное расширение под названием Buffer:

var o = new ObservableCollection<long>();
var s1 = Observable.Interval(TimeSpan.FromMilliseconds(100)).Subscribe(o.Add);
var s2 =
    Observable.FromEventPattern<NotifyCollectionChangedEventArgs>(o, "CollectionChanged")
        .Buffer(TimeSpan.FromMilliseconds(500))
        .Subscribe(
            s =>
            {
                Console.WriteLine("Last received {0}. Current count: {1}", Convert.ToInt64(s.Last().EventArgs.NewItems[0]), o.Count);                            
            });

Это позволит вам получать измененные события коллекции по мере их возникновения, но игнорировать все, кроме последнего.

ОБНОВЛЕНИЕ 1

Известно, что CollectionChanged не срабатывает при обновлении элементов. Документация MSDN просто неверна. Вам нужно будет реализовать TrulyObservableCollection, но ваши элементы данных также должны реализовать интерфейс INotifyChanged.

namespace ConsoleApplication1
{
    #region

    using System;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Linq;
    using System.Reactive.Linq;
    using System.Threading;

    #endregion

    internal class Program
    {
        #region Methods

        private static void Main(string[] args)
        {
            var autoReset = new AutoResetEvent(false);
            var r = new Random();
            var o = new TrulyObservableCollection<DataPoint>();
            var subscription1 = Observable.Interval(TimeSpan.FromSeconds(1)).Take(3).Subscribe(
                i =>
                {
                    o.Add(
                        new DataPoint
                        {
                            ItemCount = r.Next(100)
                        });
                    Console.WriteLine("Fire1 {0}", i);
                });
            var subscription2 =
                Observable.FromEventPattern<NotifyCollectionChangedEventArgs>(o, "CollectionChanged")
                    .Subscribe(s => { Console.WriteLine("List changed. Current total {0}", o.Sum(s1 => s1.ItemCount)); });
            var subscription3 = Observable.Interval(TimeSpan.FromSeconds(1)).Delay(TimeSpan.FromSeconds(3)).Take(3).Finally(
                () =>
                {
                    o.Clear();
                    autoReset.Set();
                }).Subscribe(
                    i =>
                    {
                        if (o.Any())
                        {
                            o[r.Next(o.Count)].ItemCount = r.Next(100);
                            Console.WriteLine("Fire3 {0}", i);
                        }
                    });
            autoReset.WaitOne();
        }

        #endregion

        public class TrulyObservableCollection<T> : ObservableCollection<T>
            where T : INotifyPropertyChanged
        {
            #region Constructors and Destructors

            public TrulyObservableCollection() { CollectionChanged += this.TrulyObservableCollection_CollectionChanged; }

            #endregion

            #region Methods

            private void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (e.NewItems != null)
                {
                    foreach (Object item in e.NewItems)
                    {
                        (item as INotifyPropertyChanged).PropertyChanged += this.item_PropertyChanged;
                    }
                }
                if (e.OldItems != null)
                {
                    foreach (Object item in e.OldItems)
                    {
                        (item as INotifyPropertyChanged).PropertyChanged -= this.item_PropertyChanged;
                    }
                }
            }

            private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                OnCollectionChanged(a);
            }

            #endregion
        }

        private class DataPoint : INotifyPropertyChanged
        {
            #region Fields

            private int itemCount;

            #endregion

            #region Public Events

            public event PropertyChangedEventHandler PropertyChanged;

            #endregion

            #region Public Properties

            public int ItemCount
            {
                get { return itemCount; }
                set
                {
                    itemCount = value;
                    this.OnPropertyChanged("ItemCount");
                }
            }

            #endregion

            #region Methods

            protected virtual void OnPropertyChanged(string propertyName = null)
            {
                var handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }

            #endregion
        }
    }
}
person Darek    schedule 15.07.2014
comment
И как подписаться на изменение свойств каждого элемента в Rx? - person veerendra gupta; 15.07.2014
comment
Событие ObservableCollection‹T›.CollectionChanged — происходит при добавлении, удалении, изменении, перемещении элемента или при обновлении всего списка. - person Darek; 16.07.2014
comment
Однако, основываясь на моем тестировании, он не срабатывает при изменении элементов, несмотря на то, что говорится в документации. - person Darek; 16.07.2014
comment
Следует иметь в виду одну вещь ... Расширение Buffer будет продолжать работать, даже если в коллекции нет обновлений, что застало меня врасплох. Возможно, вы захотите отменить подписку, когда больше не ожидается обновлений, например, если количество обновленных элементов остается равным 0 в течение некоторого предопределенного времени. - person Darek; 16.07.2014
comment
Еще один вопрос: когда событие OnCollectionChanged запускается с args.Reset, оно ничего не дает в e.NewItems или e.OldItems (оба являются нулевыми в случае сброса), тогда как я узнаю, какие объекты были изменены. PS: очистка всего списка и повторное добавление всех элементов было бы очень дорогой операцией в моем случае. - person veerendra gupta; 28.07.2014
comment
Ссылка: stackoverflow.com/questions/4495904/ - person veerendra gupta; 28.07.2014