모든 열거 형을 한 번에 수율로 반환합니다. 반복하지 않고


164

카드의 유효성 검사 오류를 얻는 다음 기능이 있습니다. 내 질문은 GetErrors 처리와 관련이 있습니다. 두 메소드 모두 리턴 유형이 동일합니다 IEnumerable<ErrorInfo>.

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    var errors = GetMoreErrors(card);
    foreach (var e in errors)
        yield return e;

    // further yield returns for more validation errors
}

모든 오류 GetMoreErrors를 열거 할 필요없이 반환 할 수 있습니까?

그것에 대해 생각하면 이것은 어리석은 질문 일지 모르지만, 내가 잘못되지 않도록하고 싶습니다.


나는 더 많은 수익률 질문이 나오는 것을 보게되어 기쁘다. 바보 같은 질문이 아닙니다!
JoshJordan

무엇입니까 GetCardProductionValidationErrorsFor?
Andrew Hare

4
와 무슨 잘못 반환 GetMoreErrors (카드); ?
Sam Saffron

10
@Sam : "더 유효성 검사 오류에 대한 더 수율 돌아갑니다"
존 소총

1
모호하지 않은 언어의 관점에서 한 가지 문제는 T와 IEnumerable <T>를 모두 구현하는 것이 있는지 메서드가 알 수 없다는 것입니다. 따라서 수확량에 다른 구성이 필요합니다. 즉, 이것을 할 수있는 방법이 있다면 좋을 것입니다. foo가 IEnumerable <T>를 구현하는 곳의 수율 foo를 반환합니까?
William Jockusch

답변:


141

확실히 바보 같은 질문은 아니며 F # yield!이 전체 컬렉션과 yield단일 항목에 대해 지원 하는 것입니다 . (꼬리 재귀 측면에서 매우 유용 할 수 있습니다 ...)

불행히도 C #에서는 지원되지 않습니다.

각이 반환 여러 가지 방법이있는 경우 그러나 IEnumerable<ErrorInfo>, 당신이 사용할 수있는 Enumerable.Concat코드를 간단하게하기 :

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return GetMoreErrors(card).Concat(GetOtherErrors())
                              .Concat(GetValidationErrors())
                              .Concat(AnyMoreErrors())
                              .Concat(ICantBelieveHowManyErrorsYouHave());
}

그러나 두 구현 간에는 한 가지 중요한 차이점이 있습니다. 한 번에 하나씩 리턴 된 반복자를 사용하더라도 모든 메소드를 즉시 호출합니다 . 기존 코드는 다음 오류에 대해 묻기GetMoreErrors() 전에 모든 것을 반복 할 때까지 기다립니다 .

일반적으로 이것은 중요하지 않지만 언제 어떤 일이 일어날 지 이해하는 것이 좋습니다.


3
Wes Dyer는이 패턴을 언급 한 흥미로운 기사를 가지고 있습니다. blogs.msdn.com/wesdyer/archive/2007/03/23/…
JohannesH

1
지나가는 사람에 대한 사소한 수정-System.Linq.Enumeration.Concat <> (first, second)입니다. IEnumeration.Concat ()이 아닙니다.
redcalx

@ the-locster : 무슨 말인지 잘 모르겠습니다. 열거보다는 열거 가능합니다. 당신의 의견을 명확히 할 수 있습니까?
Jon Skeet

@ Jon Skeet-메소드를 즉시 호출한다는 것은 정확히 무엇을 의미합니까? 테스트를 실행했는데 실제로 반복 될 때까지 메소드 호출을 완전히 지연시키는 것처럼 보입니다. 여기 코드 : pastebin.com/0kj5QtfD
Steven Oxley

5
@ 스티븐 : 아뇨. 그건 호출 하지만 귀하의 경우 - 방법을 GetOtherErrors()(등) 자신의 연기되는 결과를 (그들은 반복자 블록을 사용하여 구현하고 참조). 새로운 배열 또는 이와 유사한 것을 반환하도록 변경해보십시오. 내가 무슨 뜻인지 알 수 있습니다.
Jon Skeet 5

26

이와 같은 모든 오류 소스를 설정할 수 있습니다 (Jon Skeet의 답변에서 빌린 메소드 이름).

private static IEnumerable<IEnumerable<ErrorInfo>> GetErrorSources(Card card)
{
    yield return GetMoreErrors(card);
    yield return GetOtherErrors();
    yield return GetValidationErrors();
    yield return AnyMoreErrors();
    yield return ICantBelieveHowManyErrorsYouHave();
}

그런 다음 동시에 반복 할 수 있습니다.

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    foreach (var errorSource in GetErrorSources(card))
        foreach (var error in errorSource)
            yield return error;
}

또는으로 오류 소스를 병합 할 수 있습니다 SelectMany.

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return GetErrorSources(card).SelectMany(e => e);
}

의 메소드 실행 GetErrorSources도 지연됩니다.


17

나는 빠른 yield_발췌 문장을 생각해 냈습니다 .

yield_ 스니핑 사용량 애니메이션

다음은 스 니펫 XML입니다.

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Author>John Gietzen</Author>
      <Description>yield! expansion for C#</Description>
      <Shortcut>yield_</Shortcut>
      <Title>Yield All</Title>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal Editable="true">
          <Default>items</Default>
          <ID>items</ID>
        </Literal>
        <Literal Editable="true">
          <Default>i</Default>
          <ID>i</ID>
        </Literal>
      </Declarations>
      <Code Language="CSharp"><![CDATA[foreach (var $i$ in $items$) yield return $i$$end$;]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

2
이것이 어떻게 질문에 대한 답입니까?
Ian Kemp

1
@ Ian, 이것은 C #에서 중첩 수율 반환을 수행하는 방법입니다. yield!F #과 같이 없습니다 .
John Gietzen

이것은 질문에 대한 답변이 아닙니다
divyang4481

8

나는 당신의 기능에 문제가 없다고 생각합니다. 원하는대로하고 있다고 말하고 싶습니다.

Yield는 호출 될 때마다 최종 Enumeration에서 요소를 리턴하는 것으로 생각하므로, foreach 루프에 해당 요소가있을 때마다 호출 될 때마다 1 개의 요소를 리턴합니다. 결과 집합을 필터링하기 위해 foreach에 조건문을 넣을 수 있습니다. (단지 제외 기준을 제시하지 않음으로써)

나중에 메소드에서 후속 수확량을 추가하면 열거 형에 하나의 요소가 계속 추가되어 다음과 같은 작업을 수행 할 수 있습니다.

public IEnumerable<string> ConcatLists(params IEnumerable<string>[] lists)
{
  foreach (IEnumerable<string> list in lists)
  {
    foreach (string s in list)
    {
      yield return s;
    }
  }
}

4

IEnumerable<IEnumerable<T>>아무도이 코드가 지연 된 실행을 유지하도록 간단한 확장 메소드를 권장하지 않는다고 생각합니다 . 나는 여러 가지 이유로 지연된 실행의 팬입니다. 그중 하나는 거대한 맹수 열거 형이라도 메모리 풋 프린트가 작다는 것입니다.

public static class EnumearbleExtensions
{
    public static IEnumerable<T> UnWrap<T>(this IEnumerable<IEnumerable<T>> list)
    {
        foreach(var innerList in list)
        {
            foreach(T item in innerList)
            {
                yield return item;
            }
        }
    }
}

그리고 당신은 이런 식으로 당신의 경우에 그것을 사용할 수 있습니다

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return DoGetErrors(card).UnWrap();
}

private static IEnumerable<IEnumerable<ErrorInfo>> DoGetErrors(Card card)
{
    yield return GetMoreErrors(card);

    // further yield returns for more validation errors
}

마찬가지로 래퍼 함수를 ​​사용하지 않고 호출 사이트 DoGetErrors로 이동할 UnWrap수 있습니다.


2
아마도 확장 방법에 대해 DoGetErrors(card).SelectMany(x => x)전혀 생각하지 않았 으며 연기 된 동작을 유지하고 보존 하기 때문 입니다. 이것은 아담이 그의 대답 에서 제안한 것과 정확히 같습니다 .
huysentruitw

3

예, 모든 오류를 한 번에 반환 할 수 있습니다. 그냥 List<T>또는ReadOnlyCollection<T> .

반환하여 IEnumerable<T> 일련의 무언가가 반환됩니다. 컬렉션을 반환하는 것과 동일하게 보일 수 있지만 여러 가지 차이점이 있으므로 염두에 두어야합니다.

컬렉션

  • 호출자는 컬렉션이 반환 될 때 컬렉션과 모든 항목이 모두 존재하는지 확인할 수 있습니다. 호출마다 컬렉션을 만들어야하는 경우 컬렉션을 반환하는 것은 정말 나쁜 생각입니다.
  • 대부분의 컬렉션은 반환 될 때 수정할 수 있습니다.
  • 컬렉션은 유한 한 크기입니다.

시퀀스

  • 열거 될 수 있습니다-그것은 우리가 확실히 말할 수있는 거의 모든 것입니다.
  • 반환 된 시퀀스 자체는 수정할 수 없습니다.
  • 각 요소는 시퀀스를 통한 실행의 일부로 생성 될 수 있습니다 (즉 IEnumerable<T>, 반환 List<T>은 지연 평가를 허용하고 반환 은 허용 하지 않음).
  • 시퀀스는 무한 할 수 있으므로 반환해야 할 요소 수를 결정하기 위해 호출자에게 맡깁니다.

컬렉션을 반환하면 모든 클라이언트에 대해 데이터 구조를 미리 할당하기 때문에 클라이언트가 실제로 필요한 모든 컬렉션을 열거해야하는 경우 무리한 오버 헤드가 발생할 수 있습니다. 또한 시퀀스를 반환하는 다른 메서드에 위임하면 컬렉션으로 캡처하는 데 추가 복사가 필요하며 이로 인해 잠재적으로 발생할 수있는 항목 수 (및 오버 헤드 수)를 알 수 없습니다. 따라서 컬렉션이 이미있을 때 컬렉션을 반환하고 복사하지 않고 직접 반환하거나 읽기 전용으로 래핑하는 것이 좋습니다. 다른 모든 경우에는 시퀀스가 ​​더 나은 선택입니다
Pavel Minaev

동의합니다. 컬렉션을 반품하는 것이 항상 좋은 생각이라고 들었습니다. 컬렉션 반환과 시퀀스 반환 사이에 차이점이 있다는 사실을 강조하려고했습니다. 나는 그것을 더 명확하게하려고 노력할 것이다.
Brian Rasmussen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.