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>.
Trevor Pilley

3
변경 가능한 유형의 문제에 오신 것을 환영합니다. Ienumerable은 암시 적으로 변경 불가능한 데이터 유형이어야하므로 변경 구현은 LSP를 위반합니다. 이 기사는 부작용의 위험에 대한 완벽한 설명이므로 C # 유형을 가능한 한 부작용없이 작성하는 것을 연습하면 걱정할 필요가 없습니다.
Jimmy Hoffa

1
@JimmyHoffa IEnumerable는 컬렉션을 열거하는 동안 컬렉션을 수정 (추가 / 제거) 할 수 없다는 점에서 변경할 수 없지만 목록의 개체가 변경 가능한 경우 목록을 열거하는 동안 개체를 수정하는 것이 합리적입니다.
Trevor Pilley

@TrevorPilley 동의하지 않습니다. 컬렉션이 불변 인 것으로 예상되는 경우, 소비자의 기대는 외부 행위자에 의해 멤버가 변경되지 않을 것이며, 이는 부작용이라고하며 불변성 기대를 위반하는 것입니다. POLA 및 암시 적으로 LSP를 위반합니다.
Jimmy Hoffa

어때요 ToList? msdn.microsoft.com/ko-kr/library/bb342261.aspx 수율로 생성되었는지 여부를 감지하지 않지만 대신 관련성이 없습니다.
luiscubal

답변:


22

내가 이해 한 것처럼 귀하의 질문은 잘못된 전제에 기초한 것 같습니다. 추론을 재구성 할 수 있는지 살펴 보겠습니다.

  • 링크 된 기사는 자동 생성 된 시퀀스가 ​​"게으른"동작을 나타내는 방법을 설명하고 이것이 반 직관적 인 결과를 초래할 수있는 방법을 보여줍니다.
  • 따라서 IEnumerable의 지정된 인스턴스가 자동으로 생성되는지 확인하여이 게으른 동작을 나타내는 지 여부를 감지 할 수 있습니다.
  • 어떻게합니까?

문제는 두 번째 전제가 거짓이라는 것입니다. 주어진 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;
  }
}

네 가지 방법이 있습니다. 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
우리는 당신이 여기에 정답을 가질 것이라는 것을 알고 있습니다. 그러나 "참조 신원"이라는 용어에 좀 더 많은 정의를 넣을 수 있다면 좋을 수도 있지만 "불변"으로 읽었지만 C #의 불변을 모든 get 또는 value 유형마다 반환되는 새로운 객체와 혼동시키기 때문입니다. 당신이 말하는 것과 같은 것입니다. 불변성을 부여 할 수있는 다른 방법은 다양하지만 C #에서 유일하게 합리적인 방법입니다 (알고있는 방법을 잘 알 수 있습니다).
Jimmy Hoffa

2
@JimmyHoffa : Object.ReferenceEquals (x, y)가 true를 반환하면 두 개의 객체 참조 x와 y에 "참조 ID"가 있습니다.
Eric Lippert

항상 소스에서 직접 응답을 얻는 것이 좋습니다. 고마워 에릭
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>당신은 콜렉션 자체, 그 항목 만 수정하고 싶지 않아, 당신은 닷넷 4.5 인 경우) :

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

이런 식으로 누군가가와 함께 메소드를 사용하려고하면 IEnumerable<T>컴파일 타임에 실패합니다.


1
진짜 열쇠는 당신이 말한 것과 같습니다. 무언가를 바꾸고 싶을 때 수정 사항을 가진 새로운 ienumerable을 반환하십시오. 유형으로 열거 할 수있는 것은 완벽하게 실행 가능하며 변경할 이유가 없습니다.이 유형을 사용하는 기술은 말한대로 이해해야합니다. 불변 유형으로 작업하는 방법에 대해 언급하면 ​​+1입니다.
Jimmy Hoffa

1
다소 관련됨-모든 IEnumerable을 지연된 실행을 통해 수행되는 것처럼 취급해야합니다. IEnumerable 참조를 검색 할 때 사실은 실제로 열거 할 때 사실이 아닐 수 있습니다. 예를 들어, 잠금 내에서 LINQ 문을 반환하면 예기치 않은 결과가 발생할 수 있습니다.
Dan Lyons

@JimmyHoffa : 동의합니다. 한 걸음 더 나아가서 IEnumerable두 번 이상 반복하지 않도록 노력합니다 . ToList()OP에 의해 표시된 특정 경우 Double_Value를 갖는 svick의 솔루션도 IEnumerable을 반환하는 것이 더 바람직하지만 목록 을 호출 하고 반복 하여 두 번 이상 그렇게 할 필요가 없습니다 . 물론 실제 코드에서는 아마도이 LINQ유형을 생성 하는 데 사용 하고 있을 것 입니다 IEnumerable. 그러나 svick의 선택은 yieldOP의 질문과 관련하여 의미가 있습니다.
Brian

@ 브라이언 그래, 내 요점을 만들기 위해 코드를 너무 많이 변경하고 싶지 않았습니다. 실제 코드에서는을 사용했을 것 Select()입니다. 그리고 여러 반복 IEnumerable: ReSharper는 그렇게 할 때 경고 할 수 있기 때문에 도움이됩니다.
svick

5

필자는 개인적 IEnumerable으로이 기사에서 강조한 문제는 요즘 많은 C # 개발자들이 인터페이스 를 과도하게 사용했기 때문에 발생한다고 생각합니다 . 객체 컬렉션이 필요할 때 기본 유형이 된 것 같습니다. 그러나 IEnumerable결정적으로 알려지지 않았는지 여부를 결정하지 않는 많은 정보가 있습니다.

a 가 실제로 다른지 여부를 감지 해야하는 경우 추상화가 유출되어 매개 변수 유형이 너무 느슨한 상황 일 수 있습니다. 당신이하는 추가 정보가 필요한 경우에는 단독으로 제공하지 않습니다, 같은 다른 인터페이스 중 하나를 선택하여 그 요구 사항이 명시 적으로 만들 거나 .IEnumerableIEnumerableIEnumerableICollectionIList

downvoters 편집 돌아가서 질문을주의 깊게 읽으십시오. OP는 IEnumerableAPI 메소드에 전달되기 전에 인스턴스가 구체화 될 수있는 방법을 요구하고 있습니다. 내 대답은 그 질문을 다룹니다. 올바른 사용 방법과 관련하여 더 넓은 문제가 있으며 IEnumerableOP가 붙여 넣은 코드에 대한 기대는 그 광범위한 문제에 대한 오해를 기반으로하지만 OP는 올바르게 사용 IEnumerable하는 방법이나 불변 유형으로 작업하는 방법을 묻지 않았습니다. . 제기되지 않은 질문에 대답하지 않아 저를 공감하는 것은 불공평합니다.

* 나는이 용어를 사용 reify, 다른 사람이 사용 stabilize하거나 materialize. 나는 공동체가 공통의 협약을 아직 채택하지 않았다고 생각합니다.


2
하나 문제 ICollectionIList그들이 변경할 수 있습니다 (또는 적어도 살펴 방식 것을)이다. IReadOnlyCollectionIReadOnlyList닷넷 4.5에서 이러한 문제들을 해결한다.
svick

공감 비를 설명해주세요.
MattDavey

1
사용중인 유형을 변경해야한다는 데 동의하지 않습니다. Ienumerable은 훌륭한 유형이며 종종 불변성의 이점을 가질 수 있습니다. 실제 문제는 사람들이 C #에서 불변 유형으로 작업하는 방법을 이해하지 못한다는 것입니다. 당연히 상태가 매우 뛰어난 언어이므로 많은 C # 개발자에게 새로운 개념입니다.
Jimmy Hoffa

ienumerable만이 지연된 실행을 허용하므로 매개 변수로 허용하지 않으면 C #의 전체 기능 중 하나가 제거됩니다.
Jimmy Hoffa

2
잘못되었다고 생각해서 투표했습니다. SE의 정신에 있다고 생각합니다. 사람들이 잘못된 정보에 주식을 넣지 않도록하기 위해 우리는 그 답변에 대해 투표를 내려야합니다. 어쩌면 내가 틀렸을 수도 있고, 내가 틀렸다고했을 수도있다. 투표로 결정하는 것은 더 넓은 지역 사회에 달려 있습니다. 죄송합니다. 위안이되는 경우 커뮤니티에서 문제가 있다고 생각하여 옳지 않다고 생각하기 때문에 내가 작성한 부정적인 투표 답변이 많았습니다.
Jimmy Hoffa
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.