development

C #에서 사용되는 yield 키워드는 무엇입니까?

big-blog 2020. 9. 29. 08:06
반응형

C #에서 사용되는 yield 키워드는 무엇입니까?


에서 어떻게 내가 IList의 <만이 조각을 노출> 답변의 질문 중 하나는 다음과 같은 코드를 가지고 :

IEnumerable<object> FilteredList()
{
    foreach(object item in FullList)
    {
        if(IsItemInPartialList(item))
            yield return item;
    }
}

수익 키워드는 거기에서 무엇을합니까? 나는 그것이 몇 곳에서 언급되고 다른 질문 하나를 보았지만 그것이 실제로 무엇을하는지 잘 알지 못했습니다. 나는 한 스레드가 다른 스레드에 항복한다는 의미에서 yield를 생각하는 데 익숙하지만 여기서는 관련이 없어 보입니다.


yield키워드는 실제로 여기에 꽤 많이한다.

이 함수는 IEnumerable<object>인터페이스 를 구현하는 객체를 반환합니다 . 호출 함수 foreach가이 객체에 대해 시작 하면 "항복"할 때까지 함수가 다시 호출됩니다. 이것은 C # 2.0에 도입 된 구문 설탕 입니다. 이전 버전에서는 이와 같은 작업을 수행 하기 위해 자신 만의 개체 IEnumerableIEnumerator개체를 만들어야 했습니다.

이와 같은 코드를 이해하는 가장 쉬운 방법은 예제를 입력하고 중단 점을 설정 한 다음 어떤 일이 발생하는지 확인하는 것입니다. 이 예제를 단계별로 살펴보십시오.

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

예제를 Integers()살펴보면 returns에 대한 첫 번째 호출을 찾을 수 1있습니다. 두 번째 호출이 반환 2되고 라인 yield return 1이 다시 실행되지 않습니다.

다음은 실제 예입니다.

public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}

되풀이. 기능의 각 추가주기에서 사용자가 어디에 있었는지 기억하고 거기에서 선택하는 상태 머신 "내부"를 생성합니다.


수익에는 두 가지 큰 용도가 있습니다.

  1. 임시 컬렉션을 만들지 않고 사용자 지정 반복을 제공하는 데 도움이됩니다.

  2. 상태 저장 반복을 수행하는 데 도움이됩니다. 여기에 이미지 설명 입력

위의 두 가지 사항을보다 실증적으로 설명하기 위해 여기에서 볼 수있는 간단한 비디오를 만들었습니다.


최근 Raymond Chen은 yield 키워드에 대한 흥미로운 기사 시리즈를 운영했습니다.

명목상으로는 반복기 패턴을 쉽게 구현하는 데 사용되지만 상태 머신으로 일반화 할 수 있습니다. Raymond를 인용 할 필요가 없습니다. 마지막 부분은 다른 용도와도 연결되어 있습니다 (그러나 Entin의 블로그에있는 예제는 비동기 안전 코드를 작성하는 방법을 보여주는 특히 좋습니다).


첫눈에 yield return은 IEnumerable을 반환하는 .NET 설탕입니다.

수율이 없으면 컬렉션의 모든 항목이 한 번에 생성됩니다.

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}

yield를 사용하는 동일한 코드, 항목별로 항목을 반환합니다.

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}

yield 사용의 장점은 데이터를 소비하는 함수에 컬렉션의 첫 번째 항목 만 필요한 경우 나머지 항목이 생성되지 않는다는 것입니다.

수율 연산자를 사용하면 요구되는 항목을 만들 수 있습니다. 그것이 그것을 사용하는 좋은 이유입니다.


yield return열거 자와 함께 사용됩니다. yield 문을 호출 할 때마다 제어가 호출자에게 반환되지만 호출 수신자의 상태가 유지됩니다. 이로 인해 호출자가 다음 요소를 열거 할 때 문 바로 뒤에있는 yield에서 callee 메서드에서 실행을 계속 합니다.

예를 들어 이것을 이해하려고 노력합시다. 이 예에서는 각 줄에 해당하는 실행 순서를 언급했습니다.

static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

또한 각 열거에 대해 상태가 유지됩니다. Fibs()메서드에 대한 다른 호출이 있다고 가정 하면 상태가 재설정됩니다.


직관적으로 키워드는 함수를 떠나지 않고 함수에서 값을 반환합니다. 즉, 코드 예제에서는 현재 item값을 반환 한 다음 루프를 다시 시작합니다. 좀 더 공식적으로 컴파일러에서 iterator에 대한 코드를 생성하는 데 사용됩니다 . 반복자는 IEnumerable객체 를 반환하는 함수입니다 . MSDN은 여러 가지가 기사 그들에 대해합니다.


목록 또는 배열 구현은 모든 항목을 즉시로드하는 반면 yield 구현은 지연된 실행 솔루션을 제공합니다.

실제로 애플리케이션의 자원 소비를 줄이기 위해 필요한 최소한의 작업을 수행하는 것이 바람직합니다.

예를 들어, 데이터베이스에서 수백만 개의 레코드를 처리하는 애플리케이션이있을 수 있습니다. 지연된 실행 풀 기반 모델에서 IEnumerable을 사용하면 다음과 같은 이점을 얻을 수 있습니다.

  • 레코드 수가 애플리케이션의 리소스 요구 사항에 크게 영향을주지 않기 때문에 확장 성, 안정성 및 예측 가능성이 향상 될 가능성 이 높습니다.
  • 전체 컬렉션이 먼저로드 될 때까지 기다리지 않고 즉시 처리를 시작할 수 있으므로 성능과 응답 성 이 향상 될 수 있습니다.
  • 애플리케이션이 중지, 시작, 중단 또는 실패 할 수 있으므로 복구 가능성 및 활용도 가 향상 될 수 있습니다. 결과의 일부만 실제로 사용 된 모든 데이터를 미리 가져 오는 것과 비교하여 진행중인 항목 만 손실됩니다.
  • 지속적인 워크로드 스트림이 추가되는 환경에서 연속 처리 가 가능합니다.

다음은 yield를 사용하는 것과 비교하여 목록과 같은 컬렉션을 먼저 빌드하는 것 간의 비교입니다.

목록 예

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

콘솔 출력
ContactListStore : 연락처 생성 1
ContactListStore : 연락처 생성 2
ContactListStore : 연락처 생성 3
컬렉션을 반복 할 준비가되었습니다.

참고 : 목록에서 단일 항목을 요청하지 않고 전체 컬렉션이 메모리에로드되었습니다.

수율 예

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

콘솔 출력
컬렉션을 반복 할 준비가되었습니다.

참고 : 컬렉션이 전혀 실행되지 않았습니다. 이는 IEnumerable의 "지연된 실행"특성 때문입니다. 아이템 구성은 정말로 필요할 때만 발생합니다.

컬렉션을 다시 호출하고 컬렉션에서 첫 번째 연락처를 가져올 때 동작을 뒤집어 보겠습니다.

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

콘솔 출력
컬렉션을 반복 할 준비가
되었습니다. ContactYieldStore : 연락처 만들기 1
Hello Bob

좋은! 클라이언트가 컬렉션에서 항목을 "당겼을 때"첫 번째 연락처 만 구성되었습니다.


개념을 이해하는 간단한 방법은 다음과 같습니다. 기본 개념은 " foreach"을 (를) 사용할 수있는 컬렉션을 원 하지만 컬렉션에 항목을 수집하는 것은 어떤 이유로 (데이터베이스에서 쿼리하는 것과 같이) 비용이 많이 드는 경우입니다. 그리고 종종 전체 컬렉션이 필요하지 않을 것입니다. 그런 다음 컬렉션을 한 번에 한 항목 씩 빌드하고 소비자에게 다시 반환하는 함수를 만듭니다 (그런 다음 수집 작업을 일찍 종료 할 수 있음).

이렇게 생각해보세요 . 고기 카운터에 가서 얇게 썬 햄 1 파운드를 사고 싶습니다. 정육점 주인은 10 파운드의 햄을 등 뒤로 가져다가 슬라이서 기계에 올려 놓고 전체를 썰어 놓은 다음 조각 더미를 다시 가져 와서 1 파운드를 측정합니다. (오래된 방식). 를 사용 yield하면 정육점이 슬라이서 기계를 카운터로 가져 와서 각 조각을 1 파운드가 될 때까지 저울에 썰고 "양복"한 다음 포장을 완료합니다. 정육점에서는 Old Way가 더 좋을 수 있지만 (자신이 좋아하는 방식으로 기계를 구성 할 수있게 함) New Way는 대부분의 경우 소비자에게 더 효율적입니다.


yield키워드는 당신이를 만들 수 있습니다 IEnumerable<T>온 형태로 반복자 블록 . 이 반복기 블록은 지연된 실행을 지원 하며 개념에 익숙하지 않은 경우 거의 마술처럼 보일 수 있습니다. 그러나 결국에는 이상한 트릭없이 실행되는 코드 일뿐입니다.

반복자 블록은 컴파일러가 열거 형 열거가 얼마나 진행되었는지 추적하는 상태 머신을 생성하는 구문 설탕으로 설명 할 수 있습니다. 열거 형을 열거하려면 종종 foreach루프를 사용합니다 . 그러나 foreach루프는 구문상의 설탕이기도합니다. 그래서 당신은 실제 코드에서 제거 된 두 개의 추상화이기 때문에 처음에는 모든 것이 어떻게 함께 작동하는지 이해하기 어려울 수 있습니다.

매우 간단한 반복자 블록이 있다고 가정합니다.

IEnumerable<int> IteratorBlock()
{
    Console.WriteLine("Begin");
    yield return 1;
    Console.WriteLine("After 1");
    yield return 2;
    Console.WriteLine("After 2");
    yield return 42;
    Console.WriteLine("End");
}

실제 반복자 블록에는 종종 조건과 루프가 있지만 조건을 확인하고 루프를 풀면 여전히 yield다른 코드와 인터리브 문으로 끝납니다 .

반복자 블록을 열거하기 위해 foreach루프가 사용됩니다.

foreach (var i in IteratorBlock())
    Console.WriteLine(i);

출력은 다음과 같습니다 (놀랍지 않습니다).

시작
1
1 후
2
2 후
42
종료

위에서 언급했듯이 foreach구문 설탕은 다음과 같습니다.

IEnumerator<int> enumerator = null;
try
{
    enumerator = IteratorBlock().GetEnumerator();
    while (enumerator.MoveNext())
    {
        var i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    enumerator?.Dispose();
}

이것을 풀기 위해 추상화를 제거한 시퀀스 다이어그램을 만들었습니다.

C # 반복기 블록 시퀀스 다이어그램

컴파일러에 의해 생성 된 상태 머신도 열거자를 구현하지만 다이어그램을 더 명확하게하기 위해 별도의 인스턴스로 표시했습니다. (상태 머신이 다른 스레드에서 열거 될 때 실제로 별도의 인스턴스를 얻지 만 여기서는 그 세부 사항이 중요하지 않습니다.)

Every time you call your iterator block a new instance of the state machine is created. However, none of your code in the iterator block is executed until enumerator.MoveNext() executes for the first time. This is how deferred executing works. Here is a (rather silly) example:

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

At this point the iterator has not executed. The Where clause creates a new IEnumerable<T> that wraps the IEnumerable<T> returned by IteratorBlock but this enumerable has yet to be enumerated. This happens when you execute a foreach loop:

foreach (var evenNumber in evenNumbers)
    Console.WriteLine(eventNumber);

If you enumerate the enumerable twice then a new instance of the state machine is created each time and your iterator block will execute the same code twice.

Notice that LINQ methods like ToList(), ToArray(), First(), Count() etc. will use a foreach loop to enumerate the enumerable. For instance ToList() will enumerate all elements of the enumerable and store them in a list. You can now access the list to get all elements of the enumerable without the iterator block executing again. There is a trade-off between using CPU to produce the elements of the enumerable multiple times and memory to store the elements of the enumeration to access them multiple times when using methods like ToList().


If I understand this correctly, here's how I would phrase this from the perspective of the function implementing IEnumerable with yield.

  • Here's one.
  • Call again if you need another.
  • I'll remember what I already gave you.
  • I'll only know if I can give you another when you call again.

The C# yield keyword, to put it simply, allows many calls to a body of code, referred to as an iterator, that knows how to return before it's done and, when called again, continues where it left off - i.e. it helps an iterator become transparently stateful per each item in a sequence that the iterator returns in successive calls.

In JavaScript, the same concept is called Generators.


It is a very simple and easy way to create an enumerable for your object. The compiler creates a class that wraps your method and that implements, in this case, IEnumerable<object>. Without the yield keyword, you'd have to create an object that implements IEnumerable<object>.


It's producing enumerable sequence. What it does is actually creating local IEnumerable sequence and returning it as a method result


This link has a simple example

Even simpler examples are here

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

Notice that yield return won't return from the method. You can even put a WriteLine after the yield return

The above produces an IEnumerable of 4 ints 4,4,4,4

Here with a WriteLine. Will add 4 to the list, print abc, then add 4 to the list, then complete the method and so really return from the method(once the method has completed, as would happen with a procedure without a return). But this would have a value, an IEnumerable list of ints, that it returns on completion.

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

Notice also that when you use yield, what you are returning is not of the same type as the function. It's of the type of an element within the IEnumerable list.

You use yield with the method's return type as IEnumerable. If the method's return type is int or List<int> and you use yield, then it won't compile. You can use IEnumerable method return type without yield but it seems maybe you can't use yield without IEnumerable method return type.

And to get it to execute you have to call it in a special way.

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}

It's trying to bring in some Ruby Goodness :)
Concept: This is some sample Ruby Code that prints out each element of the array

 rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

어레이는 각각의 구현 방법의 수율은 호출자에 대한 제어와합니다 ( "풋 X ') 각각의 배열 요소 깔끔하게 X로 표시. 그러면 호출자는 x로 필요한 모든 작업을 수행 할 수 있습니다.

그러나 .Net 은 여기서 끝까지 가지 않습니다. C #은 멘델 트의 응답에서 볼 수 있듯이 호출자에서 foreach 루프를 작성하도록 강제하는 방식으로 IEnumerable과 yield를 결합한 것으로 보입니다. 덜 우아합니다.

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}

참고 URL : https://stackoverflow.com/questions/39476/what-is-the-yield-keyword-used-for-in-c

반응형