2012-01-26 5 views
6

현재 스트림에서 항목 모음을 읽습니다.yield가 항상 호출 됨

public class Parser{ 

private TextReader _reader; //Get set in Constructor 
private IEnumerable<Item> _items;  

public IEnumerable<Item> Items{ 
    get{ 
    //I >>thought<< this would prevent LoadItems() from being called twice. 
    return _items ?? (_items = LoadItems()); 
    } 
} 

public IEnumerable<Item> LoadItems(){ 
    while(_reader.Peek() >= 0){ 
    yield return new Item(_reader.ReadLine()); //Actually it's a little different 
    } 
} 
} 

하자 내가 두 항목을 포함하는 스트림이 있다고, 나는 다음을 수행하십시오 : 나는 다음과 같은이 작업을 수행 result2 한 동안

var textReader = //Load textreader here 
var parser = new Parser(textReader); 
var result1 = parser.Items.Count(); 
var result2 = parser.Items.Count(); 

지금 result1, 2입니다.

이제 null 체크가 쓸모 없습니까? 그것은 그 함수를 호출 할 때마다 어쨌든 나온다고 생각됩니다.

이유가 무엇인지 설명 할 수 있습니까? 그리고이 상황에 가장 적합한 해결책은 무엇이겠습니까? (내가하고있는 일이 완전한 쓰레기인지 말해주십시오 : P). LoadItems이 (yield 사용) 게으른 열거하고 필드에 할당되기 때문에

+0

그래, 그것은 모든 시간을 산출한다 그렇게는 항상 null 및 항목을로드 할 때마다 호출됩니다. –

+0

내가 틀렸다면 나에게 정정 해주세요. 그러나 여기에 그것을 지정하지 않았습니다.'_items = LoadItems()' –

답변

5

는, 당신이 _items를 열거 할 때마다 당신이 실제로 LoadItems() 내에서 루프를 일으키는 Enumerable.Count 새를 만들고있는 (즉, 다시 실행하는 것을 의미한다 Enumerator이 표시되어 다시 LoadItems 본문이 실행됩니다. LoadItems 내에서 매번 독자를 새로 만들지 않으므로 커서가 스트림 끝 부분에 위치하므로 더 이상 선을 읽지 못할 가능성이 있습니다 - null을 반환하고 하나의 Item 객체가 반환 된 것 같습니다. 두 번째 호출에는 null 문자열이 포함됩니다. (가능한 경우)

return _items ?? (_items = LoadItems().ToList()); 

또는 스트림의 시작으로 다시 독자를 원하는 상대 : 이것에

솔루션은 당신에게 구체적인 목록을 줄 것이다 Enumerable.ToList를 호출하여 LoadItems의 결과 '실현'하는 것 LoadItems이 매번 똑같이 다시 실행될 수 있습니다.

그러나이 경우에는 yield을 제거하고 구체적인 이점을 얻을 수 있으므로 약간의 이점이 있으므로 복잡도가 증가하지 않도록 권장합니다.

+0

그건 제가 생각한 것입니다. 그러나 당신이 자원을 철저히 읽고 항복 할 때, 항목을 반환합니다. 자원을 잃지 않도록 어떻게 확신 할 수 있습니까? –

+0

생성 된'IEnumerable'에서'Enumerable.ToList'를 호출하거나'List <>'(또는 다른 데이터 구조체)를 수동으로 생성하여 읽기 라인을 구체적인리스트에 구현함으로써 독자의 결과를 보존 할 수 있습니다. 예 'LoadItems(). ToList()'또는'new List (LoadItems())'를 호출합니다. –

+0

아아 감사합니다! 나는 완전히 잘못 된 결과를 얻었습니다! 설명해 주셔서 감사합니다 :) –

1

귀하의 변수 이름이 타락했습니다. 지금이 순간 : 당신은 아마 게으른 로딩되고 싶어하고 항목 저장 (변수 이름으로하는 것이 제안) 반면

private IEnumerable<Item> _items; 

당신은 게으른 로딩과 반복자을 절약 할 수 있습니다

public class Parser{ 

private TextReader _reader; //Get set in Constructor 
private List<Item> _items;  

public IEnumerable<Item> Items{ 
    get{ 
    return _items ?? (_items = LoadItems().ToList()); 
    } 
} 

private IEnumerable<Item> LoadItems(){ 
    while(_reader.Peek() >= 0){ 
    yield return new Item(_reader.ReadLine()); //Actually it's a little different 
    } 
} 
} 
1

짧은 핸드로 yield을 사용한다고 생각해보십시오. 귀하의 코드는 같은으로 전환됩니다 :

private class <>ImpossibleNameSoItWontCollide : IEnumerator<Item> 
{ 
    private TextReader _rdr; 
    /* other state-holding fields */ 
    public <>ImpossibleNameSoItWontCollide(TextReader rdr) 
    { 
    _rdr = rdr; 
    } 
    /* Implement MoveNext, Current here */ 
} 
private class <>ImpossibleNameSoItWontCollide2 : IEnumerable<Item> 
{ 
    private TextReader _rdr; 
    /* other state-holding fields */ 
    public <>ImpossibleNameSoItWontCollide2(TextReader rdr) 
    { 
    _rdr = rdr; 
    } 
    public <>ImpossibleNameSoItWontCollide GetEnumerator() 
    { 
    return new <>ImpossibleNameSoItWontCollide(_rdr); 
    } 
    /* etc */ 
} 
public IEnumerable<Item> LoadItems() 
{ 
    return new <>ImpossibleNameSoItWontCollide2(_rdr); 
} 

는 따라서 LoadItems() 실제로 한 번만 호출되지만 반환하는 객체는 GetEnumerator()가 두 번 호출있다.

TextReader이 (가) 옮겨 졌기 때문에 잘못된 결과가 나타납니다. 모든 항목을 고정하는 것보다 메모리 사용량이 적어 지므로 동일한 항목을 두 번 사용하지 않으려는 경우 이점이 있습니다.당신이이 원하는 할 때문에

, 당신이 그들을 저장하는 객체 생성해야합니다 : 어디서든 '_items'변수를 할당되지 않기 때문에

return _items = _items ?? _items = LoadItems().ToList(); 
+0

아아아, 정말이 답변을 좋아합니다. 왜냐하면 당신은 수율의 통사론이 실제로하는 일을 설명했기 때문입니다! –

+0

반갑습니다. 완성되었을 때'TextReader'를 처리하기 위해'using' 블록을'LoadItems'에 넣는다면, 열거 자의'Dispose()'메소드는 그것을 처리 할 것입니다. (그러나 열거 형은 아닙니다. 그래서 열거 형이 실제로 열거되지 않았다면 호출되지 않을 것입니다). –

관련 문제