6

this のような正しい入力および出力タイプを扱う、これに似たいくつかの質問があります。私の質問は、どのようなグッド プラクティス、メソッドの命名、パラメーターの型の選択など、遅延実行事故から保護できるものですか?

これらはIEnumerable、次の理由により、非常に一般的な引数タイプである最も一般的です。

ただし、遅延実行も導入されます。メソッド (特に拡張メソッド) を設計する際に、最も基本的な型を使用することが最善のアイデアであると考えていたのに、間違っていた可能性があります。したがって、メソッドは次のようになります。

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> lstObject)
{
    foreach (T t in lstObject)
       //some fisher-yates may be
}

明らかに危険なのは、上記の関数をレイジーLinqと混ぜ合わせて非常に影響を受けやすいことです。

var query = foos.Select(p => p).Where(p => p).OrderBy(p => p); //doesn't execute
//but
var query = foos.Select(p => p).Where(p => p).Shuffle().OrderBy(p => p);
//the second line executes up to a point.

より大きな編集:

もう一度言いますが、言語の機能に対する批判は建設的ではありませんが、良い実践方法を求めるのは StackOverflow の強みです。これを反映するように質問を更新しました。

ここで大きな編集:

上記の行を明確にするために-私の質問は、2番目の式が評価されないことではなく、真剣に評価されないことです。プログラマーはそれを知っています。私の心配は、Shuffleメソッドが実際にその時点までクエリを実行することです。何も実行されない最初のクエリを参照してください。同様に、別の Linq 式 (後で実行する必要があります) を構築するときに、カスタム関数が台無しになります。言い換えれば、呼び出し元に知らせる方法Shuffleは、Linq 式のその時点で必要な関数ではありません。ポイントが家に帰ることを願っています。申し訳ありません!:)メソッドに行って検査するのと同じくらい簡単ですが、皆さんは通常どのように防御的にプログラムするのですか..

上記の例はそれほど危険ではないかもしれませんが、要点はわかります。Linqそれは、特定の (カスタム) 関数が遅延実行の考え方にうまく適合しないことです。問題はパフォーマンスだけでなく、予期しない副作用についてもです。

しかし、このような関数は魔法のように動作しLinqます:

public static IEnumerable<S> DistinctBy<S, T>(this IEnumerable<S> source, 
                                              Func<S, T> keySelector)
{
    HashSet<T> seenKeys = new HashSet<T>(); //credits Jon Skeet
    foreach (var element in source)
        if (seenKeys.Add(keySelector(element)))
            yield return element;
}

ご覧のとおり、両方の関数が を受け取りますIEnumerable<>が、呼び出し元は関数がどのように反応するかを知りません。では、皆さんがここで取っている一般的な予防措置は何ですか?

  1. Linqカスタム メソッドに適切な名前を付けて、呼び出し元が?とうまくいくかどうかがわかるようにします。

  2. 遅延メソッドを別の名前空間に移動し、 Linq-ish を別の名前空間に保持して、少なくとも何らかのアイデアを提供しますか?

  3. IEnumerableメソッドを実行するための as パラメーターを受け入れず、immediately代わりに、より派生した型または具体的な型自体を使用してIEnumerable、遅延メソッドだけを残しますか? これにより、実行されていない可能性のある式を実行するという負担が呼び出し元にかかりますか? Linq外の世界ではIEnumerables をほとんど処理せず、ほとんどの基本的なコレクション クラスICollectionは少なくとも 1 つまで実装されているため、これは非常に可能です。

それとも何か?私は特に 3 番目のオプションが気に入っています。メソッド内でaまたは同様のLinqことを受け入れて実行する優れたプログラマーからの多くのコード (拡張メソッドに少し似ています!) を見てきました。彼らが副作用にどう対処するかはわかりません..IEnumerableToList()

編集:反対票と回答の後、プログラマーがLinqの仕組みを知らないわけではないことを明確にしたいと思います(私たちの習熟度はある程度のレベルにある可能性がありますが、それは別のことです)が、多くの関数が当時のLinqを考慮に入れました。現在、すぐに実行されるメソッドを Linq 拡張メソッドと一緒にチェーンすると危険です。だから私の質問は、Linq側から何を使用し、何を使用しないかを発信者に知らせるために、プログラマーが従う一般的なガイドラインがありますか? それは、使い方がわからない場合は助けられないというよりも、防御的にプログラミングすることです。(または少なくとも私は信じています)..

4

3 に答える 3

7

ご覧のとおり、両方の関数が を受け取りますIEnumerable<>が、呼び出し元は関数がどのように反応するかを知りません。

それは単に文書化の問題です。以下を含むDistinctByMoreLINQのドキュメントを参照してください。

このオペレーターは遅延実行を使用し、結果をストリーミングしますが、既に表示されているキーのセットは保持されます。キーが複数回表示される場合、そのキーを持つ最初の要素のみが返されます。

はい、使用する前にメンバーが何をするかを知ることが重要です。あらゆる種類のコレクションを受け入れる/返すものについては、知っておくべきさまざまな重要事項があります。

  • コレクションはすぐに読み取られますか、それとも延期されますか?
  • 結果が返される間、コレクションはストリーミングされますか?
  • 受け入れられた宣言されたコレクション型が変更可能な場合、メソッドはそれを変更しようとしますか?
  • 返された宣言されたコレクション型が変更可能である場合、実際には変更可能な実装になりますか?
  • 返されたコレクションは他のアクションによって変更されますか (たとえば、クラス内で変更される可能性があるコレクションの読み取り専用ビューですか)。
  • null許容可能な入力値ですか?
  • null許容可能な要素値ですか?
  • メソッドが返されることはありnullますか?

これらのことはすべて検討する価値があり、そのほとんどは LINQ が登場するずっと前から検討する価値がありました。

教訓は本当に、「何かを呼び出す前に、何かがどのように動作するかを確認してください」です。これは LINQ の前にも当てはまり、LINQ はそれを変更していません。以前はめったに存在しなかった 2 つの可能性 (遅延実行と結果のストリーミング) が導入されました。

于 2012-11-26T06:57:43.240 に答える
1

IEnumerableを意味のある場所で使用し、防御的にコーディングします。

SLaks がコメントで指摘したように、遅延実行はIEnumerable最初から可能でした。C# 2.0 でこのステートメントが導入されて以来、yield遅延実行を自分で実装するのは非常に簡単になりました。たとえば、このメソッドは、遅延実行を使用して乱数を返す IEnumerable を返します。

public static IEnumerable<int> RandomSequence(int length)
{
    Random rng = new Random();
    for (int i = 0; i < length; i++) {
        Console.WriteLine("deferred execution!");
        yield return rng.Next();
    }
}

したがってforeach、IEnumerable をループするために使用する場合は常に、反復の間に何かが発生する可能性があると想定する必要があります。例外をスローする可能性もあるため、 foreach ループをtry/finally.

呼び出し元が、何か危険なことを行ったり、数値を返すことを決して止めない IEnumerable を渡した場合 (無限のシーケンス)、それはあなたの責任ではありません。それを検出してエラーをスローする必要はありません。何か問題が発生した場合にメソッド自体をクリーンアップできるように、十分な数の例外ハンドラを追加するだけです。のような単純なものの場合、Shuffle何もする必要はありません。呼び出し元に例外を処理させてください。

メソッドが無限シーケンスを実際に処理できないというまれなケースでは、 のような別の型を受け入れることを検討してくださいIList。しかし、IList でさえ、遅延実行から保護することはできません。IList を実装しているクラスや、各要素を作成するためにどのような種類のブードゥーを実行しているかはわかりません。反復中に予期しないコードの実行を本当に許可できないという非常にまれなケースでは、どのような種類のインターフェイスでもなく、配列を受け入れる必要があります。

于 2012-11-26T00:13:02.623 に答える
0

遅延実行は型とは関係ありません。イテレータを使用する linq メソッドは、コードをそのように記述すると、実行が遅延される可能性があります。Select()Where()OrderByDescending()たとえば、すべてイテレータを使用するため、実行を延期します。はい、これらのメソッドは を期待していますが、それが問題IEnumerable<T>であるという意味ではありませんIEnumerable<T>

つまり、特定の (カスタム) 関数は、Linq の遅延実行の考え方とはうまくいきません。問題はパフォーマンスだけでなく、予期しない副作用についてもです。

では、皆さんがここで取っている一般的な予防措置は何ですか?

なし。正直なところ、私たちはIEnumerable どこでも使用しており、人々が「副作用」を理解していないという問題はありません. 「遅延実行という Linq の考え方」は、Linq-to-SQL などでの有用性の中心です。カスタム関数の設計は、可能な限り明確ではないように思えます。人々が LINQ を使用するコードを書いていて、それが何をしているのか理解していない場合、それが問題であり、IEnumerableたまたま基底型であるという事実ではありません。

あなたのアイデアはすべて、linq クエリを理解していないプログラマーがいるように聞こえるという事実の単なるラッパーです。遅延実行が必要ないように思える場合は、関数が終了する前にすべてを強制的に評価してください。結果に対して ToList() を呼び出し、リスト、配列、コレクション、または IEnumerables など、消費者が操作したい一貫性のある API で結果を返します。

于 2012-11-26T00:07:37.457 に答える