27

yield キーワードを使用できると思うときはいつでも、一歩下がって、それが自分のプロジェクトにどのように影響するかを調べます。yielding メソッドの状態を維持するためのオーバーヘッドがあまり得にならないと感じているので、私は常に yielding ではなくコレクションを返すことになります。コレクションを返すほとんどすべてのケースで、90% の確率で、呼び出し元のメソッドがコレクション内のすべての要素を反復処理するか、コレクション全体で一連の要素を探していると感じています。

linq での有用性は理解していますが、yield が有用な複雑なクエリ可能なオブジェクトを作成しているのは linq チームだけだと思います。

yield が便利な linq のようなものやそうでないものを書いた人はいますか?

4

14 に答える 14

28

yield を使用すると、コレクションを 1 回反復処理しますが、リストを作成すると、それを 2 回反復処理することに注意してください。

たとえば、フィルタ イテレータを考えてみましょう。

IEnumerator<T>  Filter(this IEnumerator<T> coll, Func<T, bool> func)
{
     foreach(T t in coll)
        if (func(t))  yield return t;
}

これで、これをチェーンできます:

 MyColl.Filter(x=> x.id > 100).Filter(x => x.val < 200).Filter (etc)

あなたの方法は、3 つのリストを作成 (およびトス) することです。私のメソッドはそれを一度だけ繰り返します。

また、コレクションを返すときは、ユーザーに特定の実装を強制しています。イテレータはより一般的です。

于 2008-11-25T15:05:35.310 に答える
19

linq での有用性は理解していますが、yield が有用な複雑なクエリ可能なオブジェクトを作成しているのは linq チームだけだと思います。

Yield は、.NET 2.0 に実装されるやいなや有用でした。これは、誰もが LINQ について考えるよりずっと前のことです。

なぜこの関数を書くのでしょうか:

IList<string> LoadStuff() {
  var ret = new List<string>();
  foreach(var x in SomeExternalResource)
    ret.Add(x);
  return ret;
}

yield を使用して、正当な理由もなく一時的なリストを作成する労力と複雑さを軽減できる場合:

IEnumerable<string> LoadStuff() {
  foreach(var x in SomeExternalResource)
    yield return x;
}

また、パフォーマンス上の大きな利点もあります。コードがたまたまコレクションの最初の 5 つの要素のみを使用している場合、yield を使用すると、多くの場合、その時点以降のものをロードする手間が省けます。コレクションを構築して返却すると、必要のないものをロードするために膨大な時間とスペースが浪費されます。

私は何度も行くことができました....

于 2008-12-02T23:51:22.393 に答える
12

私は最近、Expression クラスの形式で数式を表現する必要がありました。式を評価するときは、ツリー構造をポストオーダー ツリーウォークでトラバースする必要があります。これを実現するために、次のように IEnumerable<T> を実装しました。

public IEnumerator<Expression<T>> GetEnumerator()
{
    if (IsLeaf)
    {
        yield return this;
    }
    else
    {
        foreach (Expression<T> expr in LeftExpression)
        {
            yield return expr;
        }
        foreach (Expression<T> expr in RightExpression)
        {
            yield return expr;
        }
        yield return this;
    }
}

次に、単純に foreach を使用して式をトラバースします。プロパティを追加して、必要に応じてトラバーサル アルゴリズムを変更することもできます。

于 2008-11-25T17:04:39.813 に答える
11

以前の会社では、次のようなループを書いていることに気付きました。

for (DateTime date = schedule.StartDate; date <= schedule.EndDate; 
     date = date.AddDays(1))

非常に単純な反復子ブロックを使用して、これを次のように変更できました。

foreach (DateTime date in schedule.DateRange)

コードが読みやすくなりました、IMO。

于 2008-11-25T15:24:31.060 に答える
8

yieldC#2 用に開発されました (C#3 の Linq の前)。

大規模なエンタープライズ C#2 Web アプリケーションで、データ アクセスと頻繁に繰り返される計算を処理するときに、これを頻繁に使用しました。

コレクションは、複数回ヒットする要素がいくつかある場合に最適です。

ただし、多くのデータ アクセス シナリオでは、非常に大きなコレクションで必ずしも渡す必要のない多数の要素があります。

これは基本的に がSqlDataReader行うことです。これは、前方のみのカスタム列挙子です。

できることyieldは、最小限のコードで独自のカスタム列挙子をすばやく作成することです。

できることはすべてyieldC#1 で行うことができます。それを行うには大量のコードが必要でした。

Linq は実際に歩留り動作の価値を最大化しますが、これが唯一のアプリケーションではないことは確かです。

于 2008-11-25T16:12:53.723 に答える
2

私はC#のYieldの大ファンです。これは、メソッドまたはプロパティが別のIEnumerableのサブセットであるListを返すことが多い大規模な自家製フレームワークに特に当てはまります。私が見る利点は次のとおりです。

  • イールドを使用するメソッドの戻り値は不変です
  • リストを繰り返し処理するのは1回だけです
  • それは遅いまたは怠惰な実行変数です。つまり、値を返すコードは必要になるまで実行されません(ただし、何をしているのかわからない場合は、これが問題になる可能性があります)
  • ソースリストの変更のうち、別のIEnumerableを取得するために呼び出す必要はなく、IEnumeableをもう一度繰り返すだけです。
  • もっとたくさん

歩留まりのもう1つの大きな利点は、メソッドが数百万の値を返す可能性がある場合です。メソッドがリストを返す前にリストを作成するだけでメモリが不足する可能性があるほど多くあります。イールドを使用すると、メソッドは数百万の値を作成して返すことができ、呼び出し元もすべての値を格納しない限りです。したがって、大規模なデータ処理/集約操作に適しています

于 2010-01-27T15:41:19.993 に答える
2

C# の yield() の実装についてはよくわかりませんが、動的言語では、コレクション全体を作成するよりもはるかに効率的です。多くの場合、RAM よりもはるかに大きなデータセットを簡単に操作できます。

于 2008-11-25T15:46:09.160 に答える
2

関数が IEnumerable を返すときはいつでも、「yielding」を使用する必要があります。.Net > 3.0 のみではありません。

.Net 2.0 の例:

  public static class FuncUtils
  {
      public delegate T Func<T>();
      public delegate T Func<A0, T>(A0 arg0);
      public delegate T Func<A0, A1, T>(A0 arg0, A1 arg1);
      ... 

      public static IEnumerable<T> Filter<T>(IEnumerable<T> e, Func<T, bool> filterFunc)
      {
          foreach (T el in e)
              if (filterFunc(el)) 
                  yield return el;
      }


      public static IEnumerable<R> Map<T, R>(IEnumerable<T> e, Func<T, R> mapFunc)
      {
          foreach (T el in e) 
              yield return mapFunc(el);
      }
        ...
于 2008-11-25T15:21:31.520 に答える
1

yield を使用すると、「怠惰な」方法で物事を行うことができることに注意してください。怠惰とは、要素が実際に要求されるまで、IEnumberable の次の要素の評価が行われないことを意味します。これにより、いくつかの異なることを実行できるようになります。1 つは、実際に無限の計算を行う必要なく、無限に長いリストを生成できることです。次に、関数適用の列挙を返すことができます。関数は、リストを反復処理する場合にのみ適用されます。

于 2008-11-25T17:21:07.377 に答える
1

個人的には、通常の日常のプログラミングで yield を使用しているとは思いませんでした。ただし、最近 Robotics Studio のサンプルを試してみたところ、そこでは yield が広く使用されていることがわかりました。そのため、非同期と同時実行の問題がある CCR (Concurrency and Coordination Runtime) と組み合わせて使用​​されていることもわかりました。

とにかく、まだ頭を抱えようとしています。

于 2008-11-25T15:15:43.450 に答える
1

Yield は、スペースを節約できるので便利です。プログラミングのほとんどの最適化では、スペース (ディスク、メモリ、ネットワーク) と処理の間でトレードオフが行われます。プログラミング構造としての Yield を使用すると、繰り返しごとにコレクションの個別のコピーを必要とせずに、コレクションを順番に何度も繰り返すことができます。

次の例を検討してください。

static IEnumerable<Person> GetAllPeople()
{
    return new List<Person>()
    {
        new Person() { Name = "George", Surname = "Bush", City = "Washington" },
        new Person() { Name = "Abraham", Surname = "Lincoln", City = "Washington" },
        new Person() { Name = "Joe", Surname = "Average", City = "New York" }
    };
}

static IEnumerable<Person> GetPeopleFrom(this IEnumerable<Person> people,  string where)
{
    foreach (var person in people)
    {
        if (person.City == where) yield return person;
    }
    yield break;
}

static IEnumerable<Person> GetPeopleWithInitial(this IEnumerable<Person> people, string initial)
{
    foreach (var person in people)
    {
        if (person.Name.StartsWith(initial)) yield return person;
    }
    yield break;
}

static void Main(string[] args)
{
    var people = GetAllPeople();
    foreach (var p in people.GetPeopleFrom("Washington"))
    {
        // do something with washingtonites
    }

    foreach (var p in people.GetPeopleWithInitial("G"))
    {
        // do something with people with initial G
    }

    foreach (var p in people.GetPeopleWithInitial("P").GetPeopleFrom("New York"))
    {
        // etc
    }
}

(明らかに、拡張メソッドで yield を使用する必要はありません。データについて考えるための強力なパラダイムを作成するだけです。)

ご覧のとおり、これらの「フィルター」メソッドが多数ある場合 (ただし、人のリストに対してなんらかの作業を行う任意の種類のメソッドである可能性があります)、ステップごとに余分なストレージ スペースを必要とせずに、それらの多くを連鎖させることができます。 . これは、ソリューションをより適切に表現するためにプログラミング言語 (C#) を向上させる 1 つの方法です。

yield の最初の副作用は、実際に必要になるまでフィルタリング ロジックの実行を遅らせることです。したがって、タイプ IEnumerable<> の変数を (yield を使用して) 作成しても、それを反復処理しない場合、ロジックを実行したり、スペースを消費したりすることはありません。これは、強力で無料の最適化です。

もう 1 つの副作用は、yield が最も一般的なコレクション インターフェイス (IEnumerable<>) で動作することです。これにより、幅広い適用性を持つライブラリのようなコードを作成できます。

于 2008-11-25T15:19:47.653 に答える
0

System.Linq IEnumerable 拡張機能は優れていますが、さらに多くの機能が必要になる場合もあります。たとえば、次の拡張機能を考えてみましょう。

public static class CollectionSampling
{
    public static IEnumerable<T> Sample<T>(this IEnumerable<T> coll, int max)
    {
        var rand = new Random();
        using (var enumerator = coll.GetEnumerator());
        {
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current; 
                int currentSample = rand.Next(max);
                for (int i = 1; i <= currentSample; i++)
                    enumerator.MoveNext();
            }
        }
    }    
}

譲歩のもう 1 つの興味深い利点は、呼び出し元が戻り値を元のコレクション型にキャストして、内部コレクションを変更できないことです。

于 2010-09-28T13:19:29.247 に答える
0

利回りは一般的に非常に便利です。関数型プログラミングをサポートする他の言語の中でも Ruby にあるため、linq に関連付けられているようです。逆に言えば、linq はスタイルが機能的であるため、yield を使用します。

プログラムが一部のバックグラウンド タスクで大量の CPU を使用しているという問題がありました。私が本当に望んでいたのは、関数を簡単に読み取れるように、通常のように関数を記述できるようにすることでした (つまり、スレッド全体とイベントベースの引数)。また、CPU が多すぎる場合は、機能を分割することもできます。利回りはこれに最適です。私はこれについてブログ投稿を書きました。ソースはすべての人が利用できます:)

于 2008-12-02T23:32:33.227 に答える
0

私は、次のような非linqコードでyeildを使用しました(関数が同じクラスに存在しないと仮定します):

public IEnumerable<string> GetData()
{
    foreach(String name in _someInternalDataCollection)
    {
        yield return name;
    }
}

...

public void DoSomething()
{
    foreach(String value in GetData())
    {
        //... Do something with value that doesn't modify _someInternalDataCollection
    }
}

ただし、GetData() 関数が繰り返し処理しているコレクションを誤って変更しないように注意する必要があります。そうしないと、例外がスローされます。

于 2008-11-25T16:03:20.560 に答える