0

これら 2 つの実装には複雑さの大きな違いがありますか、それともコンパイラはそれを最適化しますか?

使用法:

for(int i = 0; i < int.MaxValue; i++)
{
    foreach(var item in GoodItems)
    {
        if(DoSomethingBad(item))
           break; // this is later added.
    }
}

実装 (1) :

public IEnumerable<T> GoodItems
{
   get { return _list.Where(x => x.IsGood); }
}

実装 (2) :

public IEnumerable<T> GoodItems
{
   get { foreach(var item in _list.Where(x => x.IsGood)) yield return item; }
}

IEnumerable メソッドは常に (2) を使用して実装する必要があるようです。一方が他方より優れているのはいつですか?

4

5 に答える 5

2

whereyield return内部で使用します。別のでラップする必要はありませんyield return

于 2012-08-30T04:18:48.393 に答える
2

サンプルプログラムを作成し、ILSpyを使用して出力アセンブリを調べました。2番目のオプションは、実際にはへの呼び出しをラップするWhereが、コードにゼロ値を追加する追加のクラスを生成します。コードが従わなければならない追加のレイヤーは、ほとんどのプログラムでパフォーマンスの問題を引き起こすことはおそらくありませんが、わずかに遅い速度で同じことを実行するために、すべての追加の構文を考慮してください。私の本ではそれだけの価値はありません。

于 2012-08-30T04:14:11.843 に答える
1

あなたは_list.where(x => x.IsGood);両方で行います。そうは言っても、どちらがより良い使用法でなければならないかは明らかではありませんか?

yield return使用法はありますが、このシナリオは、特にゲッターでは、1つではありません

于 2012-08-30T04:16:17.733 に答える
1

「実装 2」のペイロードのない余分なコードは、ここではあまり害がありません。

どちらのバリアントでも、プロパティ ゲッターを呼び出すたびに、望ましくない新しいオブジェクトが作成されます。したがって、2 つの連続した getter 呼び出しの結果は等しくありません。

interface IItem
{
    bool IsGood { get; set; }
}

class ItemsContainer<T>
    where T : IItem
{
    private readonly List<T> items = new List<T>();

    public IEnumerable<T> GoodItems
    {
        get { return items.Where(item => item.IsGood); }
    }

    // ...
}

// somewhere in code
class Item : IItem { /* ... */ }

var container = new ItemsContainer<Item>();
Console.WriteLine(container.GoodItems == container.GoodItems); // False; Oops!

この副作用を避ける必要があります。

class ItemsContainer<T>
    where T : IItem
{
    private readonly List<T> items;
    private readonly Lazy<IEnumerable<T>> goodItems;

    public ItemsContainer()
    {
        this.items = new List<T>();
        this.goodItems = new Lazy<IEnumerable<T>>(() => items.Where(item => item.IsGood));
    }

    public IEnumerable<T> GoodItems
    {
        get { return goodItems.Value; }
    }

    // ...
}

または、プロパティの代わりにメソッドを作成します。

public IEnumerable<T> GetGoodItems()
{
  return _list.Where(x => x.IsGood);
}

また、項目のスナップショットをクライアント コードに提供する場合、このプロパティはお勧めできません。

于 2012-08-30T06:52:25.380 に答える
0

内部的には、最初のバージョンは次のようにコンパイルされます。

public IEnumerable<T> GoodItems
{
    get
    {
        foreach (var item in _list)
            if (item.IsGood)
                yield return item;
    }
}

一方、2 つ目は次のようになります。

public IEnumerable<T> GoodItems
{
    get
    {
        foreach (var item in GoodItemsHelper)
            yield return item;
    }
}

private IEnumerable<T> GoodItemsHelper
{
    get
    {
        foreach (var item in _list)
            if (item.IsGood)
                yield return item;
    }
}

LINQのWhere句は、遅延実行で実装されます。foreach (...) yield return ...したがって、パターンを適用する必要はありません。自分自身のために、そして潜在的にランタイムのために、より多くの作業を行っています。

2 番目のバージョンが 1 番目と同じものになるかどうかはわかりません。意味的には、この 2 つ、前者が 1 ラウンドの遅延実行を行い、後者が 2 ラウンドを行うという点で異なります。これらの理由から、2番目はより複雑になると私は主張します。

質問する必要がある本当の質問は、IEnumerable を公開するときに、どのような保証を行っているかということです。単純に前方反復を提供したいと言っているのですか? それとも、インターフェースが遅延実行を提供していると述べていますか?

以下のコードでは、ランダム アクセスなしで単純に前方列挙を提供することを目的としています。

private List<Int32> _Foo = new List<Int32>() { 1, 2, 3, 4, 5 };

public IEnumerable<Int32> Foo
{
    get
    {
        return _Foo;
    }
}

しかし、ここでは不要な計算を避けたいと思います。結果が要求された場合にのみ、高価な計算を実行したいと考えています。

private List<Int32> _Foo = new List<Int32>() { 1, 2, 3, 4, 5 };

public IEnumerable<Int32> Foo
{
    get
    {
        foreach (var item in _Foo)
        {
            var result = DoSomethingExpensive(item);
            yield return result;
        }
    }
}

両方のバージョンのFoo 外観は同じに見えますが、内部実装は異なることを行います。気をつけていただきたい部分です。LINQ を使用する場合、ほとんどのオペレーターが実行を延期するため、実行の延期について心配する必要はありません。独自のコードでは、必要に応じて 1 番目または 2 番目を使用することをお勧めします。

于 2012-08-30T04:41:37.147 に答える