programing tip

C #에서 yield return iterator를 사용하는 목적 / 장점은 무엇입니까?

itbloger 2020. 10. 16. 07:12
반응형

C #에서 yield return iterator를 사용하는 목적 / 장점은 무엇입니까?


yield return x;C # 메서드 내부에서 사용한 모든 예제 는 전체 목록을 반환하는 방식으로 동일한 방식으로 수행 할 수 있습니다. 이 경우 yield return구문 을 사용하는 것 보다 목록을 반환하는 것의 이점이나 이점이 있습니까?

또한 yield return전체 목록을 반환 할 수없는 시나리오 유형은 무엇 입니까?


하지만 컬렉션을 직접 만들고 있다면 어떨까요?

일반적으로 반복자는 일련의 객체느리게 생성하는 데 사용할 수 있습니다 . 예를 들어 Enumerable.Range메서드에는 내부적으로 어떤 종류의 컬렉션도 없습니다. 요청시 다음 숫자 만 생성합니다 . 상태 머신을 사용하는이 지연 시퀀스 생성에는 많은 용도가 있습니다. 그들 대부분은 함수형 프로그래밍 개념 에서 다루어 집니다.

제 생각에는 컬렉션을 열거하는 방법으로 반복자를보고 있다면 (가장 간단한 사용 사례 중 하나 일뿐입니다) 잘못된 방향으로 가고있는 것입니다. 내가 말했듯이 반복자는 시퀀스를 반환하는 수단입니다. 시퀀스는 무한 할 수도 있습니다 . 무한 길이의 목록을 반환하고 처음 100 개 항목을 사용할 수있는 방법은 없습니다. 그것은 때때로 지연 될 수 있습니다. 컬렉션 반환은 컬렉션 생성기 (반복자) 를 반환하는 것과 상당히 다릅니다 . 사과와 오렌지를 비교하고 있습니다.

가상의 예 :

static IEnumerable<int> GetPrimeNumbers() {
   for (int num = 2; ; ++num) 
       if (IsPrime(num))
           yield return num;
}

static void Main() { 
   foreach (var i in GetPrimeNumbers()) 
       if (i < 10000)
           Console.WriteLine(i);
       else
           break;
}

이 예제는 10000 미만의 소수를 인쇄합니다. 소수 생성 알고리즘을 전혀 건드리지 않고도 백만 미만의 숫자를 인쇄하도록 쉽게 변경할 수 있습니다. 이 예제에서는 시퀀스가 ​​무한하고 소비자가 처음부터 원하는 항목 수조차 알지 못하기 때문에 모든 소수 목록을 반환 할 수 없습니다.


여기에 좋은 답변이의 혜택을 제안 yield return것입니다 당신이 목록을 만들 필요가 없습니다 ; 목록은 비쌀 수 있습니다. (또한 잠시 후에 부피가 크고 우아하지 않은 것을 알게 될 것입니다.)

하지만 목록이 없다면 어떨까요?

yield return여러 가지 방법으로 데이터 구조 (반드시 목록은 아님) 를 탐색 할 수 있습니다 . 예를 들어, 개체가 트리 인 경우 다른 목록을 만들거나 기본 데이터 구조를 변경하지 않고 사전 또는 사후 순서로 노드를 순회 할 수 있습니다.

public IEnumerable<T> InOrder()
{
    foreach (T k in kids)
        foreach (T n in k.InOrder())
            yield return n;
    yield return (T) this;
}

public IEnumerable<T> PreOrder()
{
    yield return (T) this;
    foreach (T k in kids)
        foreach (T n in k.PreOrder())
            yield return n;
}

지연 평가 / 지연된 실행

"yield return"반복자 블록은 특정 결과를 실제로 호출 할 때까지 코드를 실행 하지 않습니다 . 즉, 효율적으로 함께 연결할 수 있습니다. 팝 퀴즈 : 다음 코드가 파일에서 몇 번 반복됩니까?

var query = File.ReadLines(@"C:\MyFile.txt")
                            .Where(l => l.Contains("search text") )
                            .Select(l => int.Parse(l.SubString(5,8))
                            .Where(i => i > 10 );

int sum=0;
foreach (int value in query) 
{
    sum += value;
}

정답은 정확히 하나이며, foreach루프가 내려 가기 전까지는 아닙니다 . 별도의 linq 연산자 함수가 세 개 있어도 파일 내용을 한 번만 반복합니다.

이것은 성능 이외의 이점이 있습니다. 예를 들어, 로그 파일을 한 번 읽고 사전 필터링 하는 상당히 간단하고 일반적인 방법을 작성하고 동일한 방법을 여러 다른 위치에서 사용할 수 있으며 각 사용시 다른 필터에 추가됩니다. 따라서 코드를 효율적으로 재사용하면서 좋은 성능을 유지합니다.

무한 목록

좋은 예는이 질문에 대한 내 대답을 참조하십시오.
오류를 반환하는 C # 피보나치 함수

기본적으로 절대 멈추지 않을 반복자 블록 (적어도 MaxInt에 도달하기 전에는 안 됨)을 사용하여 피보나치 시퀀스를 구현 한 다음 해당 구현을 안전한 방식으로 사용합니다.

개선 된 의미 및 관심사 분리

위의 파일 예제를 다시 사용하면 이제 파일을 읽는 코드를 실제로 결과를 구문 분석하는 코드에서 불필요한 줄을 필터링하는 코드에서 쉽게 분리 할 수 ​​있습니다. 특히 첫 번째는 매우 재사용이 가능합니다.

이것은 단순한 시각적으로 누구보다 산문으로 설명하기가 훨씬 더 어려운 것들 중 하나입니다 1 :

문제의 명령 적 및 기능적 분리

이미지를 볼 수없는 경우 동일한 코드의 두 가지 버전이 표시되며 다른 관심사에 대한 배경이 강조 표시됩니다. linq 코드에는 모든 색상이 멋지게 그룹화되어 있지만 전통적인 명령형 코드에는 색상이 혼합되어 있습니다. 저자는이 결과가 linq를 사용하는 것보다 명령형 코드를 사용하는 것의 전형이라고 주장합니다. linq는 섹션간에 더 나은 흐름을 갖도록 코드를 더 잘 구성합니다.


1 I believe this to be the original source: https://twitter.com/mariofusco/status/571999216039542784. Also note that this code is Java, but the C# would be similar.


Sometimes the sequences you need to return are just too large to fit in the memory. For example, about 3 months ago I took part in a project for data migration between MS SLQ databases. Data was exported in XML format. Yield return turned out to be quite useful with XmlReader. It made programming quite easier. For example, suppose a file had 1000 Customer elements - if you just read this file into memory, this will require to store all of them in memory at the same time, even if they are handled sequentially. So, you can use iterators in order to traverse the collection one by one. In that case you have to spend just memory for one element.

As it turned out, using XmlReader for our project was the only way to make the application work - it worked for a long time, but at least it did not hang the entire system and did not raise OutOfMemoryException. Of course, you can work with XmlReader without yield iterators. But iterators made my life much easier (I would not write the code for import so quickly and without troubles). Watch this page in order to see, how yield iterators are used for solving real problems (not just scientific with infinite sequences).


In toy/demonstration scenarios, there isn't a lot of difference. But there are situations where yielding iterators are useful - sometimes, the entire list isn't available (e.g. streams), or the list is computationally expensive and unlikely to be needed in its entirety.


If the entire list is gigantic, it might eat a lot of memory just to sit around, whereas with the yield you only play with what you need, when you need it, regardless of how many items there are.


Take a look at this discussion on Eric White's blog (excellent blog by the way) on lazy versus eager evaluation.


Using the yield return you can iterate over items without ever having to build a list. If you don't need the list, but want to iterate over some set of items it can be easier to write

foreach (var foo in GetSomeFoos()) {
    operate on foo
}

Than

foreach (var foo in AllFoos) {
    if (some case where we do want to operate on foo) {
        operate on foo
    } else if (another case) {
        operate on foo
    }
}

You can put all of the logic for determining whether or not you want to operate on foo inside your method using yield returns and you foreach loop can be much more concise.


Here's my previous accepted answer to exactly the same question:

Yield keyword value added?

Another way to look at iterator methods is that they do the hard work of turning an algorithm "inside out". Consider a parser. It pulls text from a stream, looks for patterns in it and generates a high-level logical description of the content.

Now, I can make this easy for myself as a parser author by taking the SAX approach, in which I have a callback interface that I notify whenever I find the next piece of the pattern. So in the case of SAX, each time I find the start of an element, I call the beginElement method, and so on.

But this creates trouble for my users. They have to implement the handler interface and so they have to write a state machine class that responds to the callback methods. This is hard to get right, so the easiest thing to do is use a stock implementation that builds a DOM tree, and then they will have the convenience of being able to walk the tree. But then the whole structure gets buffered up in memory - not good.

But how about instead I write my parser as an iterator method?

IEnumerable<LanguageElement> Parse(Stream stream)
{
    // imperative code that pulls from the stream and occasionally 
    // does things like:

    yield return new BeginStatement("if");

    // and so on...
}

That will be no harder to write than the callback-interface approach - just yield return an object derived from my LanguageElement base class instead of calling a callback method.

The user can now use foreach to loop through my parser's output, so they get a very convenient imperative programming interface.

그 결과 사용자 정의 API의 양쪽이 모두 제어중인 것처럼 보이 므로 작성하고 이해하기가 더 쉽습니다.


yield를 사용하는 기본적인 이유는 자체적으로 목록을 생성 / 반환하기 때문입니다. 추가 반복을 위해 반환 된 목록을 사용할 수 있습니다.

참고 URL : https://stackoverflow.com/questions/1088442/what-is-the-purpose-advantage-of-using-yield-return-iterators-in-c

반응형