2010年10月29日

Any拡張メソッド

   このエントリーをはてなブックマークに追加 Clip to Evernote
指定した複数の値を繰り返すの記事に対して、neueccさんが、
IEnumerableを受け取るのにwhile(items.Any()) はよろしくない。
とつぶやいていました。
って、もう、半月も前のものだけれど...

なので、ちょっと書き換えてみました。
というコードを期待しているのかな?
それとも、もっと良い方法があるのかな?

まあ、Any拡張メソッドは、MSDNライブラリには、
「source の列挙は、結果が確認できるとすぐに停止します。」

とあるので、最悪でも最初の要素しかアクセスしないので、Count()メソッド使うよりも効率的だし、 僕が作成するアプリにおいては、実質的に問題になる事は無いと考えています。
それよりは、コード量が増えるほうがイヤです。



この記事へのコメント
どうもはじめまして。
会社の後輩がここを見て勉強してますって教えてくれて、最初にみたメージが "指定した複数の値を繰り返す"だったので、僕も気になってました。

neueccさんのよろしくないってのは、
Any()だと実装によっては、IEnumerable.GetEnumeratorメソッドが呼ばれる可能性があるからではないでしょうか?

IEnumerableの中身がListや配列で、そのIEnumerableのAny()で、CountプロパティやLengthプロパティを見てるかどうかもわからないですし。

ちなみに私ならこう書きます。
Enumerable.Repeat(new int[] { 1,2}, 10).SelectMany(x => x)

Repeatの回数指定がない拡張メソッド
SelectManyの引数がない拡張メソッドを作ればさらに便利ですかね。
Posted by Nazechi at 2010年10月29日 11:23
コメントありがとうございます。
おおっ、RepeatとSelectMenyの組合せはGoodですね。

でも、無限に繰り返す Repeat拡張メソッドがないので、僕が書いたメソッドでは使えないですね。

なお、なぜAnyを使っているかというと、空のシーケンスが渡された時に
無限ループしないようにしたいからです。空のシーケンスについて無視するのであれば、Anyは必要ありません。
Anyを使わないで、僕が書いたRepeatメソッドを実装する方法が残念ながら
思いつきません。

で、System.Linq.Enumerable.Any拡張メソッドの効率ですが、
どういった実装かによって効率は大きく違ってくるのは確かですね。
MSDNのドキュメントから想像すると、
Any拡張メソッドは、GetEnumerator() と MoveNext() を呼び出しているのだと思います。
なので、GetEnumerator() と MoveNext() の実装によっては、効率の問題は
出てきますね。
ただ、通常の利用の範囲では、そのような心配はいらないんじゃないかな。
Listや配列がそんなへぼな実装になっているとは思えないですし...

僕は、なにか勘違いしているかな...

Posted by Gushwell at 2010年10月29日 18:40
> 無限に繰り返す Repeat拡張メソッドがない
Reactive Extensions の EnumerableEx.Repeat を使うとか。

http://photos.bartdesmet.net/blogs/bart/archive/2009/12/30/more-linq-with-system-interactive-more-combinators-for-your-swiss-army-knife.aspx
Posted by いげ太 at 2010年10月30日 23:27
はじめまして。
Any()が良くないと言ったのは、NazechiさんのおっしゃるとおりGetEnumerator()が毎回呼ばれるからです。
そして、IEnumerable<T>は必ずしも配列やリストだけが元ソースではありません。
データベース問い合わせであったりネットワーク読み込みだったりファイルからだったり。
そのためにIEnumerable<T>はIDisposableでもあるわけですが。

その場合、一度の列挙に二度のGetEnumerator()は大きなコストがかかります。
なので、IEnumerable<T>の拡張メソッドでは原則としてGetEnumeratorの呼び出しは一度とすべき。
だと思っています。
配列やリストであることを期待する場合はIList<T>への拡張メソッドとしたほうが安全かな、とも。

無限に繰り返すのが標準にないのは確かに残念です。

RxのEnumerableEx.Repeatの挙動ですが、その日の件のつぶやきの次のつぶやきに
> RxのSystem.Interactive.dllには同じ動きをするRepeatがありますが、
> そういえばどんな実装になってるのかと見てみたら長さ0のものがSourceの場合は後続にTake(1)とか付けようと無駄でフリーズ行きでした。
と書いたのですが、つまりGushwellさんのAny()を入れた意図は満たされないところです。

Anyを使わない方法は、いささか冗長ですが

public static IEnumerable<T> Repeat<T>(this IEnumerable<T> items)
{
while (true)
{
using (var e = items.GetEnumerator())
{
if (!e.MoveNext()) yield break;
yield return e.Current;
while (e.MoveNext())
{
yield return e.Current;
}
}
}
}

などでどうでしょう。
Posted by neuecc at 2010年10月31日 00:00
すみません、追記で。
GetEnumeratorというか正確にはGetEnumerator+MoveNextで。

あと、そもそも二度の列挙すると動かなくなる(コネクションが閉じられたり)ソースもありますが、
無限Repeatしようとする時点でそれはあまり考えなくてもいい、
となると、ちょっと二度GetEnumeator呼んだからって致命的なわけではないですね。
なので絶対ダメというわけじゃなく「よろしくない」程度です。
これが外側から一回の列挙で完了しそうに見えるメソッドだった場合は「絶対ダメ」になると思っています。
Posted by neuecc at 2010年10月31日 00:11
いげ太さん
Reactive Extensions 存在は知っているのですが、まだ使ったことがありません。時間があいた時にでも調べてみようと思います。


neueccさん
DBまでは考えていませんでした。
たしかに、while (items.Any()) だと、SQLが何回も発行されることに
なりますね。
まあどちらにしても、データ取得のSQLも同じものが繰り返し発行されるので、
LINQ to SQLで使おうとすると、とんでもなく非効率になって
しまいますね。

>これが外側から一回の列挙で完了しそうに見えるメソッドだった場合は「絶対ダメ」になると思っています。

はい、これには同意します。

Posted by Gushwell at 2010年10月31日 10:17
 

この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/gushwell/52084431