2016-12-21 15 views
16

Tôi không thể tìm ra lý do tại sao mục trong danh sách lọc của tôi không được tìm thấy. Tôi đã đơn giản hóa ví dụ để hiển thị nó. Tôi có một lớp Item ...Một mục trong IEnumerable không bằng một mục trong Danh sách

public class Item 
{ 
    public Item(string name) 
    { 
     Name = name; 
    } 

    public string Name 
    { 
     get; set; 
    } 

    public override string ToString() 
    { 
     return Name; 
    } 
} 

... và một lớp 'Mặt hàng' mà phải lọc các mục và kiểm tra xem mục đầu tiên vẫn có trong danh sách ...

public class Items 
{ 
    private IEnumerable<Item> _items; 

    public Items(IEnumerable<Item> items) 
    { 
     _items = items; 
    } 

    public List<Item> Filter(string word) 
    { 
     var ret = new List<Item>(_items.Where(x => x.Name.Contains(word))); 

     Console.WriteLine("found: " + ret.Contains(_items.First())); 
     // found: false 

     return ret; 
    } 
} 

Mã thực thi trông giống như sau:

static void Main(string[] args) 
{ 
    string[] itemNames = new string[] { "a", "b", "c" }; 

    Items list = new Items(itemNames.Select(x => new Item(x))); 
    list.Filter("a"); 

    Console.ReadLine(); 
} 

Bây giờ, nếu tôi thực hiện chương trình, Console.WriteLine sẽ không tìm thấy mục đó. Nhưng tại sao?

Nếu tôi thay đổi dòng đầu tiên trong constructor để

_items = items.ToList() 

sau đó, nó có thể tìm thấy nó. Nếu tôi hoàn tác dòng đó và gọi ToList() sau trong phương pháp Lọc, nó cũng không thể tìm thấy mục đó ?!

public class Items 
{ 
    private IEnumerable<Item> _items; 

    public Items(IEnumerable<Item> items) 
    { 
     _items = items; 
    } 

    public List<Item> FilteredItems 
    { 
     get; set; 
    } 

    public List<Item> Filter(string word) 
    { 
     var ret = new List<Item>(_items.Where(x => x.Name.Contains(word))); 

     _items = _items.ToList(); 
     Console.WriteLine("found: " + ret.Contains(_items.First())); 
     // found: false 

     return ret; 
    } 
} 

Tại sao có sự khác biệt ở đâu và khi nào biểu thức lambda được thực hiện và tại sao không tìm thấy mục nào nữa? Tôi không hiểu!

+2

bạn đang khởi tạo một mục mới mỗi khi bạn gọi'x => mục mới (x))); sử dụng 'Chọn' –

+1

Xin cảm ơn các bạn. Nó rất rõ ràng, nhưng tôi không nhìn thấy nó. Tôi nên nghỉ làm hôm nay: D – melwynoo

Trả lời

20

Lý do là thực hiện hoãn lại.

Bạn intialize lĩnh vực _items để

itemNames.Select(x => new Item(x)); 

Đây là câu hỏi , không phải là câu trả lời cho câu hỏi đó. Truy vấn này được thực hiện được thực hiện mỗi khi bạn lặp lại qua _items.

Vì vậy, trong dòng này của phương pháp Filter của bạn:

var ret = new List<Item>(_items.Where(x => x.Name.Contains(word))); 

mảng nguồn được liệt kê và một new Item(x) tạo ra cho mỗi chuỗi. Các mục này được lưu trong danh sách của bạn ret.

Khi bạn gọi Contains(_items.First()) sau đó, First()lại thực hiện các truy vấn trong _items, tạo mớiItem trường hợp cho mỗi chuỗi nguồn.

Kể từ Item 's Equals phương pháp có lẽ không ghi đè và thực hiện một kiểm tra bình đẳng tham khảo đơn giản, Item đầu tiên trở về từ phiên thứ hai là một trường hợp khác nhau của Item so với cái trong danh sách của bạn.

10

Hãy loại bỏ thêm mã để xem các vấn đề:

var itemNames = new [] { "a", "b", "c" }; 
var items1 = itemNames.Select(x => new Item(x)); 
var surprise = items1.Contains(items1.First()); // False 

Bộ sưu tập items1 dường như không chứa yếu tố ban đầu của nó!(demo)

Thêm ToList() sửa chữa các vấn đề:

var items2 = itemNames.Select(x => new Item(x)).ToList(); 
var noSurprise = items2.Contains(items2.First()); // True 

Lý do tại sao bạn thấy kết quả khác nhau có và không có ToList() là (1) items1 được đánh giá một cách lười biếng, và (2) lớp Item của bạn không triển khai Equals/GetHashCode. Sử dụng ToList() làm cho công việc bình đẳng mặc định; việc thực hiện kiểm tra bình đẳng tùy chỉnh sẽ khắc phục vấn đề cho việc liệt kê nhiều lần.

Bài học chính từ bài tập này là lưu trữ IEnumerable<T> được truyền cho hàm tạo của bạn là nguy hiểm. Đây chỉ là một trong những lý do; các lý do khác bao gồm nhiều điều tra và có thể sửa đổi chuỗi sau mã của bạn đã xác thực tính năng nhập của nó. Bạn nên gọi ToList hoặc ToArray trên chuỗi thông qua thành nhà thầu để tránh những vấn đề này:

public Items(IEnumerable<Item> items) { 
    _items = items.ToList(); 
} 
4

Có hai vấn đề trong mã của bạn.

Vấn đề đầu tiên là bạn đang khởi tạo một mục mới mỗi lần. Đó là bạn không lưu trữ các mặt hàng thực tế ở đây khi bạn viết.

IEnumerable<Item> items = itemNames.Select(x => new Item(x)); 

Thực hiện Select được hoãn lại. tức là mỗi khi bạn gọi .ToList(), một bộ Mục mới được tạo bằng cách sử dụng itemNames làm nguồn.

Vấn đề thứ hai là bạn đang so sánh các mục theo tham chiếu tại đây.

Console.WriteLine("found: " + ret.Contains(_items.First())); 

Khi bạn sử dụng ToList bạn lưu trữ các mục trong danh sách và tài liệu tham khảo vẫn giống nhau, do đó bạn sẽ tìm thấy mục với tài liệu tham khảo.

Khi bạn không sử dụng ToList, các tham chiếu không giống nhau nữa. bởi vì mỗi khi một Item mới được tạo ra. bạn không thể tìm thấy mục của bạn với tham chiếu khác nhau.

Các vấn đề liên quan