STL/CLR の collection_adapter は要注意
STL/CLR には,cliext::collection_adapter というクラスが導入され,IEnumerable<T> をラップして C++ のイテレータとして扱うことが出来るようになりました.以下のエントリで επιστημη さんも書かれていますが,たとえば STL の algorithm に適用したい場合などに便利と.
しかし,この cliext::collection_adapter,あんまり深く考えずに使うと IEnumerable<T> を実装していれば必ず IDisposable である理由 を台無しにしてしまうので,結構使い方が難しいと思うのですよ.
たとえば,C++/CLI で以下のような 5 パターンの ForEach メソッドを作ってみます.
using namespace System; using namespace System::Collections::Generic; #include <cliext\list> #include <cliext\algorithm> #include <cliext\adapter> namespace STLCLRTest { public ref class Util { public: static void ForEach1(IEnumerable<System::String ^>^ strlist, Action<System::String ^>^ action) { for each(System::String ^ str in strlist) { action(str); } } static void ForEach2(IEnumerable<System::String ^>^ strlist, Action<System::String ^>^ action) { cliext::collection_adapter< IEnumerable<System::String ^> > itr(strlist); cliext::for_each(itr.begin(), itr.end(), action ); } static void ForEach3(IEnumerable<System::String ^>^ strlist, Action<System::String ^>^ action) { cliext::collection_adapter< IEnumerable<System::String ^> > itr(strlist); cliext::for_each(itr.begin(), itr.end(), action ); delete itr; } static void ForEach4(IEnumerable<System::String ^>^ strlist, Action<System::String ^>^ action) { cliext::collection_adapter< IEnumerable<System::String ^> > itr(strlist); try { cliext::for_each(itr.begin(), itr.end(), action ); } finally { } delete itr; } static void ForEach5(IEnumerable<System::String ^>^ strlist, Action<System::String ^>^ action) { cliext::collection_adapter< IEnumerable<System::String ^> > itr(strlist); try { cliext::for_each(itr.begin(), itr.end(), action ); } finally { delete itr; } } }; }
んではこれを C# から使ってみましょう.
using System; using System.Collections.Generic; using System.Linq; using System.Text; class Program { static IEnumerable<string> TestItr { get { try { yield return "1"; yield return "2"; } finally { Console.WriteLine("Finish!"); } } } static void Main(string[] args) { var funcs = new Action<IEnumerable<string>, Action<string>>[] { STLCLRTest.Util.ForEach1, STLCLRTest.Util.ForEach2, STLCLRTest.Util.ForEach3, STLCLRTest.Util.ForEach4, STLCLRTest.Util.ForEach5, }; foreach (var func in funcs) { try { Console.WriteLine(func.Method.Name); func(TestItr, str => { throw new Exception(); }); } catch {} } } }
列挙中に例外が発生したとき,果たして C# イテレータの finally が呼ばれるかというテストです.これはすなわち,列挙中断時に IEnumerator<T>.Dispose が確定的に呼び出されるか,ということに帰着されます.
んで,結果はこの通り.Orcas beta 2 調べです.
ForEach1
Finish!
ForEach2
ForEach3
ForEach4
ForEach5
Finish!
なんか予想に反して ForEach5 で Dispose が呼ばれているのが気になりますが,多分バグだろうということでスルーしつつ*1,この結果を見る限り STL/CLR は,IEnumerable<T> のセマンティクスに敬意を払っていないんじゃないか,というわけです.
*1:connect サイトにはフィードバックを入れておきましたのでご安心を.追記.「お前のコードがバグってる」言われました.まあそうなんでしょうが.「C++/CLI……恐ろしい子!」