12

私は、副作用と、それらをどのように制御および適用すべきかについての理解を深めようとしています。

次のフライトリストでは、条件を満たす各フライトのプロパティを設定したいと思います。

IEnumerable<FlightResults> fResults = getResultsFromProvider();

//Set all non-stop flights description
fResults.Where(flight => flight.NonStop)
        .Select(flight => flight.Description = "Fly Direct!");

この式では、リストに副作用があります。私の限られた知識から、私は元のために知っています。「LINQはクエリにのみ使用されます」および「リストへの操作はわずかであり、値の割り当てまたは設定はそれらの1つではありません」および「リストは不変である必要があります」。

  • 上記のLINQステートメントの何が問題になっていますか?また、どのように変更する必要がありますか?
  • 上記のシナリオの基本的なパラダイムに関する詳細情報はどこで入手できますか?
4

6 に答える 6

14

LINQ の方法でそれを達成するには、次の 2 つの方法があります。

  1. 明示的なforeachループ

    foreach(Flight f in fResults.Where(flight => flight.NonStop))
      f.Description = "Fly Direct!";
    
  2. ForEach副作用のために作られた演算子で:

    fResults.Where(flight => flight.NonStop)
            .ForEach(flight => flight.Description = "Fly Direct!");
    

最初の方法は、このような単純なタスクには非常に負荷がかかります。2 番目の方法は、非常に短いボディでのみ使用する必要があります。

ForEachここで、LINQ スタックに演算子がない理由を自問するかもしれません。それは非常に単純です - LINQ は、クエリ操作を表現する機能的な方法であると想定されています。これは特に、どの演算子も副作用を想定していないことを意味します。ForEach設計チームは、唯一の使用法がその副作用であるため、スタックにオペレーターを追加しないことに決めました。

演算子の通常の実装は次のForEachようになります。

public static class EnumerableExtension
{
  public static void ForEach<T> (this IEnumerable<T> source, Action<T> action)
  {
    if(source == null)
      throw new ArgumentNullException("source");

    foreach(T obj in source)
      action(obj);

  }
}
于 2011-06-17T13:12:58.167 に答える
9

このアプローチの問題の1つは、まったく機能しないことです。クエリは怠惰です。つまり、クエリから実際に何かを読み取るまで、Selectのコードは実行されず、決して実行されません。

クエリの最後に追加することでこれを回避でき.ToList()ますが、コードは依然として副作用を使用しており、実際の結果を破棄しています。代わりに、結果を使用して更新を行う必要があります。

//Set all non-stop flights description
foreach (var flight in fResults.Where(flight => flight.NonStop)) {  
  flight.Description = "Fly Direct!";
}
于 2011-06-17T13:16:21.497 に答える
6

リスト自体を変更していないため、LINQコードは言及したガイドラインに「直接」違反していません。リストの内容の一部のプロパティを変更しているだけです。

ただし、これらのガイドラインを推進する主な反論は残っています: LINQ を使用してデータを変更するべきではありません (また、Select副作用を実行するために悪用しています)。

データを変更しないことは、非常に簡単に正当化できます。次のスニペットを検討してください。

fResults.Where(flight => flight.NonStop)  

これがフライト プロパティを変更している場所がわかりますか? 多くのメンテナンス プログラマーもそうではありません。なぜなら、彼らは次の後に読むのをやめてしまうからWhereです -- これはクエリなので、次のコードには 明らかに副作用がありませんよね?

[ちょっとしたこと: 確かに、戻り値が保持されていないクエリを見ることは、クエリに副作用があるか、コードを削除する必要があることを示しています。とにかく「何かがおかしい」。しかし、ページごとのページではなく、コードが 2 行しかない場合は、そう簡単に言うことができます。]

正しい解決策として、これをお勧めします:

foreach (var x in fResults.Where(flight => flight.NonStop))
{
    x.Description = "Fly Direct!";
}

書き込みと読み取りの両方が非常に簡単です。

于 2011-06-17T13:18:31.187 に答える
2

foreach実際に何かを変更するときに使用するのが好きです。何かのようなもの

foreach (var flight in fResults.Where(f => f.NonStop))
{
  flight.Description = "Fly Direct!";
}

また、Eric Lippert は、LINQ に ForEach ヘルパー メソッドがない理由についての記事でそう述べています。

しかし、ここでもう少し深く掘り下げることができます。私は、2 つの理由から、そのような方法を提供することに哲学的に反対しています。

最初の理由は、他のすべてのシーケンス演算子が基づいている関数型プログラミングの原則に違反することです。明らかに、このメソッドを呼び出す唯一の目的は、副作用を引き起こすことです。

于 2011-06-17T13:12:44.500 に答える
2

それを呼び出すなど、何らかの形で反復する必要があることを除いて、それ自体に問題はありませんCount()

「スタイル」の観点からは、良くありません。イテレータがリストの値/プロパティを変更するとは思わないでしょう。

IMOは次のほうが良いでしょう:

foreach (var x in fResults.Where(flight => flight.NonStop))
{
  x.Description = "Fly Direct!";
}

その意図は、コードの読者やメンテナーにとってより明確です。

于 2011-06-17T13:13:00.567 に答える
2

これを 2 つのコード ブロックに分割する必要があります。1 つは取得用、もう 1 つは値の設定用です。

var nonStopFlights = fResults.Where(f => f.NonStop);

foreach(var flight in nonStopFlights)
    flight.Description = "Fly Direct!";

または、 foreach の外観が本当に嫌いな場合は、次を試すことができます。

var nonStopFlights = fResults.Where(f => f.NonStop).ToList();

// ForEach is a method on List that is acceptable to make modifications inside.
nonStopFlights.ForEach(f => f.Description = "Fly Direct!");
于 2011-06-17T13:13:07.240 に答える