どー言うことだってばよ
追加で検証するため、EPPlusのソースコード読んでいたら、おやと思うことがあったので、とりあえずまとめておく。
何が起きるのか
下記のような操作をしたとする。
using System;
using System.IO;
using OfficeOpenXml;
namespace EPPlusSurvey
{
class Program
{
static void Main(string[] args)
{
using (var package = new ExcelPackage(new FileInfo("Sample.xlsx")))
{
var sheet = package.Workbook.Worksheets[1];
//A1:B10の矩形範囲にはすべて値が詰まっている。
var range = sheet.Cells["A1:B10"];
var foo = range.GetEnumerator();
var bar = range.GetEnumerator();
foo.MoveNext();
foo.MoveNext();
bar.MoveNext();
bar.MoveNext();
//こいつはTrueになる。
Console.WriteLine(ReferenceEquals(foo, bar));
//だもんで、foo及びbarをMoveNextした結果が、それぞれにに反映される。
//foo:B2
Console.WriteLine($"foo:{foo.Current.Address}");
//bar:B2
Console.WriteLine($"bar:{bar.Current.Address}");
}
}
}
}
コメントでネタバレしてるけど、GetEnumeratorで取得できるEnumeratorが独立していない。
なので、上記サンプルで、foo
及びbar
に対する操作の結果が、それぞれに反映されてしまうことになる(つまり、MoveNextの結果が累積している)。
このような操作をするシナリオはそんなに無いだろうけど、ステートの保持を目的としてEnumeratorを保持して、長い間使い回したりすると、問題が起きるかも知れないのでご注意の程。
回避策
じゃあ、回避策はあるのかというと、range
変数を使わずに、都度都度sheet.Cells
プロパティでRangeを取得すれば回避できる。
using System;
using System.IO;
using OfficeOpenXml;
namespace EPPlusSurvey
{
class Program
{
static void Main(string[] args)
{
using (var package = new ExcelPackage(new FileInfo("Sample.xlsx")))
{
var sheet = package.Workbook.Worksheets[1];
//A1:B10の矩形範囲にはすべて値が詰まっている。
var rangeA = sheet.Cells["A1:B10"];
var rangeB = sheet.Cells["A1:B10"];
//Falseになる。
Console.WriteLine(ReferenceEquals(rangeA, rangeB));
var foo = rangeA.GetEnumerator();
var bar = rangeB.GetEnumerator();
foo.MoveNext();
foo.MoveNext();
bar.MoveNext();
//当然こいつはFalseになる。
Console.WriteLine(ReferenceEquals(foo, bar));
//共々独立しているのでめでたしめでたし
//foo:B1
Console.WriteLine($"foo:{foo.Current.Address}");
//bar:A1
Console.WriteLine($"bar:{bar.Current.Address}");
}
}
}
}
こんなケースはまれだろうけど、一般的なGetEnumeratorの戻り値と異なっているので、その点注意しないとマズいかと思って一応まとめてみた。