none
Исключение ArgumentOutOfRangeException при вставке элемента в ObservableCollection<T> RRS feed

  • Вопрос

  • Добрый день всем.

    У меня при вставке элемента в ObservableCollection<T> отбрасывается исключение ArgumentOutOfRangeException. В коллекции храню экземпляры своего пользовательского класса OpenBookRecord. Вот его определение:

    /// <summary>
    ///  Класс OpenBookRecord представляет запись
    ///  из таблицы агрегированного стакана.
    /// </summary>
    public class OpenBookRecord : IComparable<OpenBookRecord>, INotifyPropertyChanged
    {
            #region Events
    
            /// <summary>
            /// Событие "Изменилось значение свойства"
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
    
            #endregion
    
            #region Fields
    
            /// <summary>
            /// Значение ReplID записи.
            /// </summary>
            private Int64 replId;
            /// <summary>
            /// Значение ReplRev записи.
            /// </summary>
            private Int64 replRev;
            /// <summary>
            /// Цена котировки.
            /// </summary>
            private Double price;
            /// <summary>
            /// Объем агрегированной котировки.
            /// </summary>
            private Int64 volume;
            /// <summary>
            /// Направление котировки (покупка/продажа).
            /// </summary>
            private SByte dir;
            /// <summary>
            /// Ссылка на стакан, содержащий эту запись
            /// и поле кода инструмента.
            /// </summary>
            private OpenBook residence;
    
            #endregion
    
            #region PropertyChanged Event Raiser
    
            /// <summary>
            ///  Вызывает событие "Изменилось значение свойства".
            /// </summary>
            /// <param name="propertyName"></param>
            private void NotifyPropertyChanged(String propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    
            #endregion
    
            #region Properties
    
            /// <summary>
            /// Возвращает или задаёт значение ReplID записи.
            /// </summary>
            public Int64 ReplId
            {
                get { return replId; }
                set
                {
                    if (replId != value)
                    {
                        replId = value;
                        //NotifyPropertyChanged("ReplId");
                    }
                }
            }
            /// <summary>
            /// Возвращает или задаёт значение ReplRev записи.
            /// </summary>
            public Int64 ReplRev
            {
                get { return replRev; }
                set
                {
                    if (replRev != value)
                    {
                        replRev = value;
                        //NotifyPropertyChanged("ReplRev");
                    }
                }
            }
            /// <summary>
            /// Возвращает или задаёт цену котировки.
            /// </summary>
            public Double Price
            {
                get { return price; }
                set
                {
                    if (price != value)
                    {
                        price = value;
                        NotifyPropertyChanged("Price");
                    }
                }
            }
            /// <summary>
            /// Возвращает или задаёт объем агрегированной котировки.
            /// </summary>
            public Int64 Volume
            {
                get { return volume; }
                set
                {
                    if (volume != value)
                    {
                        volume = value;
                        NotifyPropertyChanged("Volume");
                    }
                }
            }
            /// <summary>
            /// Возвращает или задаёт 
            /// </summary>
            public SByte Dir
            {
                get { return dir; }
                set
                {
                    if (dir != value)
                    {
                        dir = value;
                        NotifyPropertyChanged("Dir"); // Раскомментировал в целях отладки!!!
                    }
                }
            }
            /// <summary>
            /// Возвращает или задаёт ссылку на стакан, содержащий эту запись
            /// и поле кода инструмента.
            /// </summary>
            public OpenBook Residence
            {
                get { return residence; }
                set
                {
                    if (residence != value)
                    {
                        residence = value;
                        //NotifyPropertyChanged("Residence");
                    }
                }
            }
    
            #endregion
    
            #region Methods
    
            /// <summary>
            /// Предикат, указывающий порядок сортировки по умолчанию записей в стакане.
            /// Согласно этому предикту - записи сортируются по возрастанию значения
            /// в поле ReplID
            /// </summary>
            /// <param name="cmpRecord"></param>
            /// <returns></returns>
            public int CompareTo(OpenBookRecord cmpRecord)
            {
                return replId.CompareTo(cmpRecord.replId);
            }
    
            #endregion
    }

    Работаю с ObservableCollection<T> следующим образом. Создал класс, производный от неё:

    /// <summary>
    /// Класс ObservableOpenBook представляет содержимое агрегированного стакана,
    /// предназначенное для визуализации в окне приложения.
    /// </summary>
    public class ObservableOpenBook : ObservableCollection<OpenBookRecord>
    {
           /// <summary>
           /// Создаёт экземпляр класса ObservableOpenBook.
           /// </summary>
           public ObservableOpenBook()
               : base()
           {
           }
    }

    В классе главного окна приложения определил поле типа ObservableOpenBook:

    private OpenBook tmpOb;
    /// <summary>
    /// Стакан по выбранному для торгов инструменту,
    /// содержимое которого предназначено для визуализации в окне приложения.
    /// </summary>
    private static ObservableOpenBook displayedOpenBook = new ObservableOpenBook();

    И, затем, в том месте приложения, где я ловлю информацию с биржи, я загружаю коллекцию данными:

    // Заполнить экранное представление стакана:
    // Если в записей в стакане столько же или больше,
    // чем в его экранном представлении:
    if (tmpOb.OpenBookRecords.Count >= displayedOpenBook.Count)
    {
          for (int i = 0; i < tmpOb.OpenBookRecords.Count; i++)
          {
                if (i < displayedOpenBook.Count)
                {
                      displayedOpenBook[i] = tmpOb.OpenBookRecords[i];
                }
                else
                {
                      displayedOpenBook.Add(tmpOb.OpenBookRecords[i]);
                }
          }
    }

    tmpOb - это экземпляр моего пользовательского класса OpenBook ниже привожу его определение:

    /// <summary>
    /// Класс OpenBook представляет агрегированный стакан,
    /// по одному торгуемому инструменту.
    /// </summary>
    public class OpenBook : IComparable<OpenBook>
    {
            #region Fields
    
            /// <summary>
            /// Список записей в стакане по торгуемому инструменту
            /// (покупка и продажа).
            /// </summary>
            private List<OpenBookRecord> openBookRecords;
            /// <summary>
            /// Код торгуемого инструмента.
            /// </summary>
            private Int64 isin_Id;
            /// <summary>
            /// Флаг, показывающий, что в стакане произошли изменения true.
            /// </summary>
            private Boolean f_Changed;
    
            #endregion
    
            #region Properties
    
            /// <summary>
            /// Возвращает или задаёт список записей в стакане по торгуемому инструменту.
            /// (покупка и продажа).
            /// </summary>
            public List<OpenBookRecord> OpenBookRecords
            {
                get { return openBookRecords; }
                set { openBookRecords = value; }
            }
            /// <summary>
            /// Возвращает или задаёт код торгуемого инструмента.
            /// </summary>
            public Int64 InstrumentCode
            {
                get { return isin_Id; }
                set { isin_Id = value; }
            }
            /// <summary>
            /// Возвращает или задаёт флаг, показывающий,
            /// что в стакане произошли изменения true.
            /// </summary>
            public Boolean IsChanged
            {
                get { return f_Changed; }
                set { f_Changed = value; }
            }
    
            #endregion
    
            #region Methods
    
            /// <summary>
            /// Создаёт экземпляр класса OpenBook.
            /// </summary>
            public OpenBook()
            {
                // Создаём список, который будет хранить записи
                // в стакане по инструменту.
                openBookRecords = new List<OpenBookRecord>();
                // Задаём условия сортировки записей в стакане
                // по направлению котировки и убыванию цены
                DomComparer sorterByDirectionAndPrice = new DomComparer();
                // и сортируем записи в стакане согласно этим условиям.
                openBookRecords.Sort(sorterByDirectionAndPrice);
            }
    
            /// <summary>
            /// Добавляет запись в стакан по инструменту.
            /// </summary>
            /// <param name="addedRecord"></param>
            public void AddRecordToOpenBook(OpenBookRecord addedRecord)
            {
                // Индекс записи, по которому будет производится
                // её вставка в список.
                Int32 idx;
    
                if (addedRecord != null)
                {
                    // Если новое значение цены не нулевое
                    if (addedRecord.Price > 0)
                    {
                        // Задаём условия для проверки - есть ли эта запись в стакане
                        DomComparer sorterByDirectionAndPrice = new DomComparer();
                        // Выполняем эту проверку
                        idx = openBookRecords.BinarySearch(addedRecord, sorterByDirectionAndPrice);
                        // В обоих случаях выполняем вставку записи
                        if (idx >= 0)
                        {
                            openBookRecords.Insert(idx, addedRecord);
                        }
                        else
                        {
                            openBookRecords.Insert(~idx, addedRecord);
                        }
                    }
                }
                // Показываем, что состояние стакана по инструменту изменилось.
                f_Changed = true;
            }
    
            /// <summary>
            /// Удаляет запись из стакана по инструменту.
            /// </summary>
            /// <param name="removedRecord"></param>
            public void RemoveRecordFromOpenBook(OpenBookRecord removedRecord)
            {
                // Удалить запись из списка, представляющего стакан по инструменту.
                openBookRecords.Remove(removedRecord);
                // Показать, что стакан по инструменту был изменен
                // (всвязи с удалением записи).
                f_Changed = true;
            }
    
            /// <summary>
            /// Предикат, указывающий порядок сортировки по умолчанию
            /// стаканов по инструменту в списке стаканов. Согласно
            /// которому стаканы по инструментам сортируются по возрастанию
            /// значения кода инструмента.
            /// </summary>
            /// <param name="tmpOpenBook"></param>
            /// <returns></returns>
            public int CompareTo(OpenBook tmpOpenBook)
            {
                return isin_Id.CompareTo(tmpOpenBook.isin_Id);
            }
    
            #endregion
    }
    Как видно из определения OpenBook, OpenBookRecords - это список  List<OpenBookRecord> openBookRecords, доступ к которому идёт через открытое свойство.

    Когда у меня возникало исключение, то я проверял значение индекса i у displayedOpenBook[i] и у tmpOb.OpenBookRecords[i] и сравнивал его с свойством Count и там и там. Значение Count у обеих коллекций было равно 64. Значение i в обеих коллекциях равнялось 63. Посмотрел описание свойства Item у ObservableCollection. Там написано, что ArgumentOutOfRangeException возникает, если значение индекса меньше нуля или больше или равно Count. Но у меня же значение индекса 63, а Count == 64. Т.е. на единицу больше, чем i. Почему же тогда в строке

    displayedOpenBook[i] = tmpOb.OpenBookRecords[i];

    Возникает ArgumentOutOfRangeException ?


    • Изменено TownSparrow 27 ноября 2012 г. 8:45
    27 ноября 2012 г. 8:43

Ответы

  • Привет.

    Очевидно, что в момент возникновения исключения в tmpOb.OpenBookRecords значение i=tmpOb.OpenBookRecords.Count. Почему и как, сказать не могу, надо вникать. Но могу посоветовать просто исключить этот случай, т.е при заполнении данными изменить строку

     if (i < displayedOpenBook.Count)

    на

     if (i < displayedOpenBook.Count &&  i<tmpOb.OpenBookRecords.Count)

    или же создать свое исключение, чтобы поймать момент, когда и почему это происходит

    if (i < displayedOpenBook.Count)
       {
          if (i == tmpOb.OpenBookRecords.Count) 
                        throw new ArgumentOutOfRangeException("tmpOb.OpenBookRecords.Count", "MyArgumentOutOfRangeException");
          displayedOpenBook[i] = tmpOb.OpenBookRecords[i];
       }


    • Помечено в качестве ответа TownSparrow 30 ноября 2012 г. 15:03
    30 ноября 2012 г. 11:34

Все ответы

  • Привет.

    А параллельных потоков у вас нет, которые могут изменять эту коллекцию?


    Для связи [mail]

    27 ноября 2012 г. 20:54
  • Нет. Эта коллекция изменяется только внутри одного треда. Причём, та коллекция, из которой в неё присваиваются данные тоже изменяется только внутри этого же треда и присваивание происходит в то время, когда исходная коллекция (из которой присваивается) закончила своё очередное изменение. Причём в отладчике я никак не могу подловить причину возникновения этого исключения. Оно происходит крайне редко и если происходит, то и происходит в как раз при отладке по шагам, когда "курсор " отладчика (или как там его правильно назвать - текущая отлаживаемая строчка) находится совершенно в другом месте. По крайней мере таково моё зрительное ощущение.






    • Изменено TownSparrow 28 ноября 2012 г. 16:12
    28 ноября 2012 г. 15:59
  • Привет.

    Очевидно, что в момент возникновения исключения в tmpOb.OpenBookRecords значение i=tmpOb.OpenBookRecords.Count. Почему и как, сказать не могу, надо вникать. Но могу посоветовать просто исключить этот случай, т.е при заполнении данными изменить строку

     if (i < displayedOpenBook.Count)

    на

     if (i < displayedOpenBook.Count &&  i<tmpOb.OpenBookRecords.Count)

    или же создать свое исключение, чтобы поймать момент, когда и почему это происходит

    if (i < displayedOpenBook.Count)
       {
          if (i == tmpOb.OpenBookRecords.Count) 
                        throw new ArgumentOutOfRangeException("tmpOb.OpenBookRecords.Count", "MyArgumentOutOfRangeException");
          displayedOpenBook[i] = tmpOb.OpenBookRecords[i];
       }


    • Помечено в качестве ответа TownSparrow 30 ноября 2012 г. 15:03
    30 ноября 2012 г. 11:34
  • Спасибо. Буду пробовать. А пока пинимаю ваш совет как ответ.


    • Изменено TownSparrow 30 ноября 2012 г. 15:03
    30 ноября 2012 г. 15:02