문제

에서 IList<>의 일부만 노출하려면 어떻게 해야 합니까? 질문 답변 중 하나에는 다음 코드 조각이 있습니다.

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

여기서 Yield 키워드는 무엇을 합니까?나는 그것이 몇 군데 장소와 다른 질문에서 참조되는 것을 보았지만 그것이 실제로 무엇을 하는지는 잘 알지 못했습니다.나는 하나의 스레드가 다른 스레드에 양보한다는 의미에서 양보를 생각하는 데 익숙했지만 여기서는 관련이 없는 것 같습니다.

도움이 되었습니까?

해결책

그만큼 yield 키워드는 실제로 여기서 꽤 많은 일을 합니다.

이 함수는 다음을 구현하는 객체를 반환합니다. IEnumerable<object> 상호 작용.호출 기능이 시작되면 foreach이 객체에 대해 함수는 "결과를 얻을" 때까지 다시 호출됩니다.이것은 다음에 소개된 구문 설탕입니다. C# 2.0.이전 버전에서는 직접 만들어야 했습니다. IEnumerable 그리고 IEnumerator 이런 일을 하는 개체.

이와 같은 코드를 이해하는 가장 쉬운 방법은 예제를 입력하고 중단점을 설정한 후 무슨 일이 일어나는지 확인하는 것입니다.다음 예제를 단계별로 시도해 보세요.

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() 보고 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);
                }
            }
        }
    }
}

다른 팁

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

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

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

  2. 상태 저장 반복을 수행하는 데 도움이 됩니다.enter image description here

위의 두 가지 사항을 보다 실증적으로 설명하기 위해 시청하실 수 있는 간단한 동영상을 만들었습니다. 여기

최근 Raymond Chen은 수익률 키워드에 관한 흥미로운 기사 시리즈도 게재했습니다.

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

얼핏 보면 항복 반환은 IEnumerable을 반환하는 .NET 설탕입니다.

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

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 연산자를 사용하면 요구되는 대로 항목을 생성할 수 있습니다.그것이 그것을 사용하는 좋은 이유입니다.

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

예를 들어 이것을 이해해 봅시다.이 예에서는 각 줄에 해당하는 실행 흐름 순서를 언급했습니다.

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 값을 입력한 다음 루프를 다시 시작합니다.보다 공식적으로는 컴파일러에서 코드를 생성하는 데 사용됩니다. 반복자.반복자는 다음을 반환하는 함수입니다. 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();
    }

콘솔 출력
연락처목록저장소:연락처 1 만들기
연락처목록저장소:연락처 2 생성 중
연락처목록저장소:연락처 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();
}

콘솔 출력
컬렉션을 반복할 준비가 되었습니다.
연락처YieldStore:연락처 1 만들기
안녕하세요 밥

멋진!클라이언트가 컬렉션에서 항목을 "끌어올" 때 첫 번째 연락처만 구성되었습니다.

개념을 이해하는 간단한 방법은 다음과 같습니다.기본 아이디어는 "foreach"를 사용하지만 데이터베이스에서 항목을 쿼리하는 것과 같은 어떤 이유로 컬렉션에 항목을 모으는 데 비용이 많이 들고 전체 컬렉션이 필요하지 않은 경우가 많습니다. 그런 다음 한 번에 한 항목씩 컬렉션을 빌드하는 함수를 생성하고 이를 소비자에게 돌려줍니다(그러면 소비자는 수집 노력을 조기에 종료할 수 있습니다).

이렇게 생각해보세요: 당신은 정육점에 가서 얇게 썬 햄 1파운드를 사고 싶습니다.정육점 주인은 10파운드짜리 햄을 뒤로 가져가서 슬라이서 기계에 놓고 전체를 얇게 썬 다음 조각 더미를 다시 가져와 1파운드를 측정합니다.(오래된 방식).와 함께 yield, 정육점 주인은 슬라이서 기계를 카운터로 가져오고 1파운드가 될 때까지 각 조각을 저울 위에 썰고 "굴곡"한 다음 포장해 주면 작업이 완료됩니다. 정육점 주인에게는 기존 방식이 더 나을 수 있지만(그가 원하는 방식으로 기계를 구성할 수 있음), 대부분의 경우 소비자에게는 새로운 방식이 분명히 더 효율적입니다.

그만큼 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);

출력은 다음과 같습니다(여기서는 놀랄 일이 아닙니다).

Begin
1
After 1
2
After 2
42
End

상술 한 바와 같이 foreach 구문 설탕은 다음과 같습니다.

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

이 문제를 해결하기 위해 추상화가 제거된 시퀀스 다이어그램을 만들었습니다.

C# iterator block sequence diagram

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

반복자 블록을 호출할 때마다 상태 머신의 새 인스턴스가 생성됩니다.그러나 반복자 블록의 코드는 다음까지 실행되지 않습니다. enumerator.MoveNext() 처음으로 실행됩니다.이것이 지연 실행이 작동하는 방식입니다.다음은 (다소 어리석은) 예입니다.

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

이 시점에서는 반복자가 실행되지 않았습니다.그만큼 Where 절은 새로운 것을 생성합니다 IEnumerable<T> 그것은 포장 IEnumerable<T> 에 의해 반환됨 IteratorBlock 하지만 이 열거 가능한 항목은 아직 열거되지 않았습니다.이는 다음을 실행할 때 발생합니다. foreach 고리:

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

열거 가능 항목을 두 번 열거하면 상태 머신의 새 인스턴스가 매번 생성되고 반복기 블록은 동일한 코드를 두 번 실행합니다.

다음과 같은 LINQ 메서드에 주목하세요. ToList(), ToArray(), First(), Count() 등.을 사용할 것이다 foreach 열거 가능한 항목을 열거하는 루프입니다.예를 들어 ToList() 열거 가능한 모든 요소를 ​​열거하고 목록에 저장합니다.이제 반복자 블록을 다시 실행하지 않고도 목록에 액세스하여 열거 가능 항목의 모든 요소를 ​​가져올 수 있습니다.다음과 같은 메서드를 사용할 때 열거형 요소를 여러 번 생성하기 위해 CPU를 사용하는 것과 열거형 요소에 여러 번 액세스하기 위해 메모리를 저장하는 것 사이에는 절충점이 있습니다. ToList().

이 내용을 올바르게 이해했다면 Yield를 사용하여 IEnumerable을 구현하는 함수의 관점에서 이를 표현하는 방법은 다음과 같습니다.

  • 여기 하나가 있습니다.
  • 다른 것이 필요하면 다시 전화하세요.
  • 나는 이미 당신에게 무엇을 주었는지 기억할 것입니다.
  • 당신이 다시 전화할 때 내가 또 다른 것을 줄 수 있는지만 알겠습니다.

간단히 말해서 C# Yield 키워드는 반복자라고 하는 코드 본문에 대한 많은 호출을 허용합니다. 이 코드는 완료되기 전에 반환하는 방법을 알고 다시 호출하면 중단된 부분부터 계속됩니다.이는 반복기가 연속 호출에서 반환하는 시퀀스의 각 항목마다 반복기가 투명하게 상태를 유지하는 데 도움이 됩니다.

JavaScript에서는 동일한 개념을 제너레이터(Generator)라고 합니다.

이는 개체에 대한 열거형을 만드는 매우 간단하고 쉬운 방법입니다.컴파일러는 메서드를 래핑하고 이 경우 IEnumerable<object>를 구현하는 클래스를 만듭니다.Yield 키워드가 없으면 IEnumerable<object>를 구현하는 개체를 만들어야 합니다.

열거 가능한 시퀀스를 생성하고 있습니다.실제로 수행하는 작업은 로컬 IEnumerable 시퀀스를 생성하고 이를 메서드 결과로 반환하는 것입니다.

이것 링크 간단한 예가 있습니다

더 간단한 예는 여기에 있습니다.

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

수익률 반환은 메서드에서 반환되지 않습니다.당신은 심지어 WriteLineyield return

위의 내용은 4개의 정수 4,4,4,4의 IEnumerable을 생성합니다.

여기에는 WriteLine.목록에 4를 추가하고 abc를 인쇄한 다음 목록에 4를 추가한 다음 메서드를 완료하고 실제로 메서드에서 반환합니다(메서드가 완료되면 반환 없는 프로시저에서 발생하는 것처럼).하지만 이것은 가치가 있을 것입니다. IEnumerable 목록 ints, 완료되면 반환됩니다.

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

또한 Yield를 사용할 때 반환하는 내용은 함수와 동일한 유형이 아닙니다.이는 IEnumerable 목록.

메소드의 반환 유형과 함께 Yield를 다음과 같이 사용합니다. IEnumerable.메소드의 반환 유형이 다음과 같은 경우 int 또는 List<int> 그리고 당신은 사용 yield, 그러면 컴파일되지 않습니다.당신이 사용할 수있는 IEnumerable Yield 없이 메소드 반환 유형을 사용하지만 아마도 Yield 없이는 Yield를 사용할 수 없는 것 같습니다. IEnumerable 메소드 반환 유형.

그리고 이를 실행하려면 특별한 방법으로 호출해야 합니다.

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");
}

Ruby Goodness를 가져오려고 노력하고 있습니다 :)
개념: 이것은 배열의 각 요소를 인쇄하는 샘플 Ruby 코드입니다.

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

배열의 각 메소드 구현 수확량 다음을 사용하여 호출자에게 제어권을 부여합니다('x를 넣습니다'). 배열의 요소는 x로 깔끔하게 표시됩니다.그러면 호출자는 x와 관련하여 필요한 모든 작업을 수행할 수 있습니다.

하지만 .그물 여기까지 안가는데..C#은 Mendelt의 응답에서 볼 수 있듯이 호출자에 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]; 
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top