モノトーンの伝説日記

Apex Legends, Splatoon, Programming, and so on...

<mini> IEnumerable<T> は遅延実行されます。

 調査してたらこんな問題が…

 前、StatefulModel を魔改造して使っているといってましたが、ロック回数とか減らすために AddRange とかの複数オブジェクトを一度に追加するバージョンのメソッドを追加しているんです (ろくにテストはしてないんですけどね)

 今までだとこう実装してたわけですよ。

public void AddRange(IEnumerable<T> items)
{
  if (items == null) throw new ArgumentNullException(nameof(items));
  if (items.Count() == 0) throw new ArgumentException(nameof(items));

  ReadAndWriteWithLockAction(
    () =>
    {
      foreach (var item in items)
      {
        this._list.Add(item);
      }
    },
    () =>
    {
      this.OnPropertyChanged("Count");
      this.OnPropertyChanged("Item[]");
      this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items.ToArray(), this._list.Count - itemsList.Length));
    });
}

 でまあ IE<T> の忘れがちな落とし穴で遅延実行なわけじゃないですか。でまあ、foreachToArray() で二回動くんで、別々のオブジェクトができるんですよね。正しくは以下のように実装しました。

public void AddRange(IEnumerable<T> items)
{
  if (items == null) throw new ArgumentNullException(nameof(items));

  var count = items.Count();
  if (count == 0) throw new ArgumentException(nameof(items));

  // 予め配列にでも書きだしておく
  var ary = new T[count];
  var idx = 0;
  foreach (var item in items) ary[idx++] = item;
  ReadAndWriteWithLockAction(
    () =>
    {
      foreach (var item in ary)
      {
        this._list.Add(item);
      }
    },
    () =>
    {
      this.OnPropertyChanged("Count");
      this.OnPropertyChanged("Item[]");
      this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, ary.ToArray(), this._list.Count - itemsList.Length));
    });
}

 やらかしてしまった事象ですね。いかんです。