備忘録的な何か

技術ブログ的な何かです

C#のiEnumerable#SelectはEnumerator::Lazy#mapだった件

C#.net framework 3.5より、LINQ用のEnumerableメソッドが追加されています。

Select,Where,OrderByなどです。

これらのメソッドは、LINQでの使用はもちろん、ラムダ式を渡して、利用することができます。これは、Rubyのmapやfilterに当たるものになります。

参考:http://www.atmarkit.co.jp/ait/articles/1107/22/news141.html

ここで、iEnumerable#Selectのザックリとした実装を見てみますと、以下のようになっています。

public static IEnumerable<TResult> Select<TSource, TResult>(
   this IEnumerable<TSource> source, Func<TSource, TResult> block)
  {
   foreach (TSource e in source)
    yield return block(e);
  }

ここで、問題になるのが、yield return block(e);の部分になります。この構文は、実際にその要素が利用されるまで、blockを実行しません。すなわち、遅延評価となっています。LINQの実装から考えると当然ですね。

参考:http://ufcpp.net/study/csharp/sp3_lazylist.html

ですので、次のようなコードを書くとハマります。

   int y = 0;
   var l = new list<int>{1,2,3}.Select((x) => y += x);
   Console.WriteLine(y);//y= 0 (6になると思いきや、まだ評価されていない)
   l.Last();//コレで全部評価される。
   Console.WriteLine(y);//y= 6

そういえば、これはどこかで聞いたことのある話のような...。あー、Enumerator::Lazyですね。分かります。

参考:http://magazine.rubyist.net/?0041-200Special-lazy

ということで、Rubyのmap,filter相当のメソッドは実は存在しませんので、使いたいのであれば、自分で定義するしかないようです。こんな感じかな。

  public static IEnumerable<TResult> Map<TSource, TResult>(
   this IEnumerable<TSource> source,
   Func<TSource, TResult> block)
  {
   List<TResult> l = new List<TResult>();
   foreach (TSource e in source)
    l.Add( block( e ) );
   return l;
  }

  public static IEnumerable<TSource> Filter<TSource>(
   IEnumerable<TSource> source,
   Func<TSource, bool> predicate)
  {
   List<TSource> l = new List<TSource>();
   foreach (var item in source)
   {
    if (predicate(item) )
    {
     l.Add(item);
    }
   }
   return l;
  }