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
하나 문제 ICollection와 IList그들이 변경할 수 있습니다 (또는 적어도 살펴 방식 것을)이다. IReadOnlyCollection및 IReadOnlyList닷넷 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.