2012-01-26 9 views
6

Hiện tại tôi đang đọc một bộ sưu tập các mục từ một luồng. Tôi làm điều này như sau:sản lượng luôn được gọi là

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 
    } 
} 
} 

Hãy nói rằng tôi có một dòng, trong đó có hai mục, và tôi thực hiện như sau:

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

Bây giờ result1 là 2, trong khi result2 là một.

Bây giờ tôi nhận thấy rằng kiểm tra vô giá trị của tôi là vô ích? Dường như mỗi lần tôi gọi hàm đó, nó được mang lại.

Ai đó có thể giải thích cho tôi biết tại sao điều này là? Và những gì sẽ là giải pháp tốt nhất cho tình huống này (xin vui lòng cho tôi biết nếu những gì tôi đang làm là hoàn thành crap: P).

+0

yup, cần mang mỗi lần kể từ khi bạn không gán biến '_items' bất cứ nơi nào để nó luôn vô các mục tải được gọi mỗi lần. –

+0

Hãy sửa tôi nếu tôi sai nhưng tôi không gán nó ở đây '_items = LoadItems()' –

Trả lời

5

LoadItems là lười biếng đếm được (sử dụng yield) và bạn đang gán nó vào một lĩnh vực, nó có nghĩa là mỗi khi bạn liệt kê _items bạn đang thực sự gây ra vòng lặp trong LoadItems() được chạy một lần nữa, tức là (Enumerable.Count tạo ra một mới Enumerator mỗi lần làm cho cơ thể LoadItems chạy lại). Vì bạn không tạo trình đọc lặp lại mỗi lần trong vòng LoadItems con trỏ của nó sẽ được đặt ở cuối luồng nên có khả năng không thể đọc thêm bất kỳ dòng nào - Tôi nghi ngờ rằng nó đang trả về null và đối tượng Item duy nhất của bạn được trả về cuộc gọi thứ hai chứa một chuỗi null.

Giải pháp này sẽ được 'nhận ra' kết quả của LoadItems bằng cách gọi Enumerable.ToList mà sẽ cung cấp cho bạn một danh sách cụ thể:

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

Hoặc tìm kiếm người đọc trở về đầu dòng (nếu có thể) sao cho LoadItems có thể chạy lại mỗi lần giống nhau.

Nhưng tôi khuyên bạn chỉ cần loại bỏ các yield ing trong trường hợp này và trả về một danh sách cụ thể vì có rất ít lợi ích, do đó bạn đang trả giá phức tạp cho không đạt được.

+0

Đó là những gì tôi đã tìm. Nhưng khi bạn đọc hết tài nguyên và trả lại lợi nhuận cho các mục. Làm thế nào bạn có thể chắc chắn rằng bạn không bị mất tài nguyên. –

+0

Bạn có thể giữ kết quả của người đọc bằng cách nhận ra các dòng đọc thành danh sách cụ thể, bằng cách gọi 'Enumerable.ToList' trên kết quả« IEnumerable' hoặc bằng cách xây dựng một 'List <>' (hoặc cấu trúc dữ liệu khác) theo cách thủ công, ví dụ 'LoadItems(). ToList()' hoặc 'Danh sách mới (LoadItems())'. –

+0

Aah cảm ơn! Tôi hoàn toàn có ý tưởng về sản lượng sai! Cảm ơn bạn đã giải thích nó :) –

1

Tên biến của bạn đã khiến bạn lạc lối. Tại thời điểm này:

private IEnumerable<Item> _items; 

bạn là lười biếng nạp và lưu iterator, trong khi bạn có thể muốn được lười biếng nạp và lưu mục (như tên biến gợi ý):

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

Hãy xem xét rằng yield được sử dụng làm bàn tay ngắn. code của bạn bị biến thành một cái gì đó như:

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

Do đó LoadItems() thực sự là chỉ gọi một lần, nhưng đối tượng nó sẽ trả về có GetEnumerator() kêu gọi nó hai lần.

Và kể từ khi TextReader đã chuyển sang, điều này sẽ cho kết quả sai cho bạn. Mặc dù lưu ý rằng nó dẫn đến việc sử dụng bộ nhớ thấp hơn là giữ tất cả các mục, vì vậy nó có lợi khi bạn không muốn sử dụng cùng một tập hợp các mục hai lần.

Vì bạn làm muốn, bạn cần tạo một đối tượng mà các cửa hàng họ:

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

Ah tuyệt vời, tôi thực sự thích câu trả lời này bởi vì bạn đã giải thích những gì đường cú pháp của năng suất thực sự làm! –

+0

Bạn được chào đón. Đó là giá trị gia tăng, rằng nếu bạn đặt một khối 'using' vào' LoadItems' để vứt bỏ 'TextReader' khi bạn đã hoàn thành, thì phương thức' Dispose() 'của điều tra viên sẽ bỏ nó (nhưng không phải là kiểu liệt kê, vì vậy nếu điều tra không thực sự được liệt kê, thì nó sẽ không được gọi). –