IEnumerableの「ステートマシン」の検出


17

はc#yield returnを使用して可愛すぎるという興味深い記事を読みました

IEnumerableが実際の列挙可能なコレクションであるか、またはyieldキーワードで生成されたステートマシンであるかを検出する最良の方法は何なのかと思いました。

たとえば、(記事の)DoubleXValueを次のように変更できます。

private void DoubleXValue(IEnumerable<Point> points)
{
    if(points is List<Point>)
      foreach (var point in points)
        point.X *= 2;
    else throw YouCantDoThatException();
}

質問1)これを行うより良い方法はありますか?

質問2)これは、APIを作成するときに心配する必要があるものですか?


1
ICollection<T>すべてのコレクションがであるとは限らないため、具体的ではなくインターフェースを使用し、さらに下にキャストすることをお勧めしますList<T>。たとえば、配列はPoint[]実装されますIList<T>が、そうではありませんList<T>
トレバーピリー

3
可変型の問題へようこそ。Ienumerableは暗黙的に不変のデータ型であると想定されているため、実装の変更はLSPに違反します。この記事は副作用の危険性を完全に説明しているため、C#型をできるだけ副作用のないように書く練習をしてください。これについて心配する必要はありません。
ジミー・ホッファ

1
@JimmyHoffa IEnumerableは、列挙中にコレクションを変更(追加/削除)できないという意味で不変ですが、リスト内のオブジェクトが変更可能な場合は、リストの列挙中にそれらを変更することは完全に合理的です。
トレバーピリー

@TrevorPilley同意しません。コレクションが不変であると予想される場合、消費者からの期待は、メンバーが外部のアクターによって変異されないことです。これは副作用と呼ばれ、不変性の期待に違反します。POLAおよび暗黙的にLSPに違反します。
ジミー・ホッファ

使用してみてはToListいかがですか?msdn.microsoft.com/en-us/library/bb342261.aspxそれはyieldによって生成されたかどうかを検出しませんが、代わりに無関係にします。
ルイスキューバル

回答:


22

私の理解では、あなたの質問は間違った前提に基づいているようです。推論を再構築できるかどうか見てみましょう。

  • リンク先の記事では、自動生成されたシーケンスが「怠lazな」動作をどのように示すかを説明し、これが直感に反する結果をもたらす方法を示しています。
  • したがって、IEnumerableの特定のインスタンスがこの遅延動作を示すかどうかは、自動的に生成されるかどうかを確認することで検出できます。
  • それ、どうやったら出来るの?

問題は、2番目の前提が間違っていることです。与えられたIEnumerableがイテレータブロック変換の結果であるかどうかを検出できたとしても(そして、そうする方法はあります)、仮定が間違っているので助けにはなりません。理由を説明しましょう。

class M { public int P { get; set; } }
class C
{
  public static IEnumerable<M> S1()
  {
    for (int i = 0; i < 3; ++i) 
      yield return new M { P = i };
  }

  private static M[] ems = new M[] 
  { new M { P = 0 }, new M { P = 1 }, new M { P = 2 } };
  public static IEnumerable<M> S2()
  {
    for (int i = 0; i < 3; ++i)
      yield return ems[i];
  }

  public static IEnumerable<M> S3()
  {
    return new M[] 
    { new M { P = 0 }, new M { P = 1 }, new M { P = 2 } };
  }

  private class X : IEnumerable<M>
  {
    public IEnumerator<X> GetEnumerator()
    {
      return new XEnum();
    }
    // Omitted: non generic version
    private class XEnum : IEnumerator<X>
    {
      int i = 0;
      M current;
      public bool MoveNext()
      {
        current = new M() { P = i; }
        i += 1;
        return true;
      }
      public M Current { get { return current; } }
      // Omitted: other stuff.
    }
  }

  public static IEnumerable<M> S4()
  {
    return new X();
  }

  public static void Add100(IEnumerable<M> items)
  {
    foreach(M item in items) item.P += 100;
  }
}

よし、4つの方法があります。S1およびS2は自動的に生成されるシーケンスです。S3およびS4は手動で生成されたシーケンスです。今、私たちが持っていると仮定します:

var items = C.Sn(); // S1, S2, S3, S4
S.Add100(items);
Console.WriteLine(items.First().P);

S1とS4の結果は0になります。シーケンスを列挙するたびに、作成されたMへの新しい参照を取得します。S2とS3の結果は100になります。シーケンスを列挙するたびに、前回取得したMへの同じ参照を取得します。シーケンスコードが自動的に生成されるかどうかは、列挙されたオブジェクトに参照IDがあるかどうかの問題とは直交します。自動生成と参照IDの2つのプロパティは、実際には互いに関係ありません。あなたがリンクした記事はそれらを幾分圧縮します。

参照IDを持つオブジェクトを常に提供するものとしてシーケンスプロバイダーが文書化されていない限り、そうすることを想定するのは賢明ではありません。


2
ここで正しい答えが得られることは誰もが知っています。それは当然のことです。しかし、「参照アイデンティティ」という用語にもう少し定義を加えることができれば、それは「不変」と読んでいますが、それはC#で不変をすべてのgetまたは値型が返される新しいオブジェクトと融合するためですあなたが言及しているのと同じものです。付与された不変性は他のさまざまな方法で実現できますが、C#で唯一の合理的な方法です(私が知っていることですが、私が知らない方法をよく知っているかもしれません)。
ジミー・ホッファ

2
@JimmyHoffa:Object.ReferenceEquals(x、y)がtrueを返す場合、2つのオブジェクト参照xとyには「参照ID」があります。
エリックリッパー

ソースから直接応答を得るのは常に良いことです。エリックに感謝します。
ConditionRacer

10

重要な概念は、IEnumerable<T>コレクションを不変として扱うことだと思います。オブジェクトを変更しないでください。新しいオブジェクトで新しいコレクションを作成する必要があります。

private IEnumerable<Point> DoubleXValue(IEnumerable<Point> points)
{
    foreach (var point in points)
        yield return new Point(point.X * 2, point.Y);
}

(または、LINQを使用して同じことをより簡潔に記述しSelect()ます。)

実際にコレクション内のアイテムを変更したい場合はIEnumerable<T>、使用しないでくださいIList<T>(またはIReadOnlyList<T>、コレクション自体を変更したくない場合、コレクション内のアイテムのみで、.Net 4.5を使用している場合):

private void DoubleXValue(IList<Point> points)
{
    foreach (var point in points)
        point.X *= 2;
}

このようにして、誰かがでメソッドを使用しようとするとIEnumerable<T>、コンパイル時に失敗します。


1
実際のキーは、あなたが言ったように、何かを変更したいときは、修正された新しい数えられるものを返します。型としてのIenumerableは完全に実行可能であり、変更する理由はありません。それは、あなたが言ったようにそれを使用するためのテクニックを理解する必要があるだけです。不変の型を扱う方法に言及するための+1。
ジミー・ホッファ

1
多少関連があります-また、すべてのIEnumerableを遅延実行によって行われるかのように扱う必要があります。IEnumerable参照を取得する時点で真実である可能性があるのは、実際に列挙するときに真実でない場合があります。たとえば、ロック内でLINQステートメントを返すと、興味深い予期しない結果が生じる可能性があります。
ダンライオンズ

@JimmyHoffa:同意します。さらに一歩進めて、IEnumerable2回以上の繰り返しを避けるようにします。ToList()リストを呼び出して反復処理することで、2回以上行う必要がなくなりますが、OPで示される特定のケースでは、DoubleXValueがIEnumerableを返すというsvickのソリューションを好みます。もちろん、実際のコードでは、おそらくLINQこのタイプのを生成するために使用しIEnumerableます。ただし、svickの選択yieldは、OPの質問のコンテキストで意味があります。
ブライアン

@ブライアンええ、私は自分の主張をするためにあまりコードを変更したくありませんでした。実際のコードでは、を使用していましたSelect()。また、IEnumerableReSharperの複数の繰り返しに関しては、ReSharperが役立ちます。これを行うと警告が表示されるためです。
svick

5

個人的には、この記事で強調された問題は、IEnumerable最近の多くのC#開発者によるインターフェースの過剰使用に起因すると考えています。オブジェクトのコレクションが必要な場合、これはデフォルトのタイプになったようです-しかし、IEnumerable単にあなたに伝えない多くの情報があります...決定的に:それが具体化されたかどうか*。

が本当に他の何かであるかどうかを検出する必要があることわかった場合、抽象化が漏れており、おそらくパラメータータイプが少し緩すぎる状況にあります。単独では提供されない追加情報が必要な場合は、またはなどの他のインターフェイスのいずれかを選択して、その要件を明示的に設定します。IEnumerableIEnumerableIEnumerableICollectionIList

downvotersの編集 戻って、質問を注意深く読んでください。OPはIEnumerable、APIメソッドに渡される前にインスタンスが具体化されることを保証する方法を求めています。私の答えはその質問に対処します。適切な使用方法に関する幅広い問題がありIEnumerable、OPが貼り付けたコードの期待は確かにその広範な問題の誤解に基づいていますが、OPは正しく使用する方法IEnumerable、または不変型を操作する方法を尋ねませんでした。提起されなかった質問に答えないことで私に投票するのは公平ではありません。

* 私は用語を使用しreify、他はstabilizeまたはを使用しますmaterialize。コミュニティで共通の慣習がまだ採用されているとは思わない。


2
一つの問題ICollectionとはIList、彼らが可変(そのようにまたは少なくとも見て)していることです。IReadOnlyCollectionそして、IReadOnlyListネット4.5から、これらの問題の一部を解決します。
svick

downvoteを説明してください?
マットデイビー

1
使用しているタイプを変更する必要があることに同意しません。Ienumerableは素晴らしいタイプであり、不変性の利点をしばしば持つことができます。本当の問題は、C#で不変型を扱う方法を人々が理解していないことだと思います。驚くことではありませんが、非常にステートフルな言語であるため、多くのC#開発者にとって新しい概念です。
ジミー・ホッファ

ienumerableのみが遅延実行を許可するので、パラメータとしてそれを禁止すると、C#の1つの機能全体が削除されます
ジミーホッファ

2
私はそれが間違っていると思うので、私はそれを採決しました。これはSEの精神に基づいていると思います。人々が間違った情報に株を入れないようにするために、それらの答えに賛成票を投じることは私たち全員の責任です。たぶん私は間違っている、私が間違っている可能性は完全にあり、あなたは正しい。それは投票によって決定するより広いコミュニティ次第です。あなたが個人的にそれを取っていることを申し訳ありませんが、それが何らかの慰めである場合、私は多くの否定的な投票された回答を書いており、私はコミュニティがそれが間違っていると思うので受け入れられないので、すぐに削除しました。
ジミー・ホッファ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.