2009-07-23 27 views
18

Tôi luôn luôn giả định rằng nếu tôi đang sử dụng Select(x=> ...) trong ngữ cảnh LINQ cho đối tượng, thì bộ sưu tập mới sẽ được tạo ngay lập tức và giữ nguyên. Tôi không hoàn toàn chắc chắn TẠI SAO tôi giả định điều này, và nó là một giả định rất xấu nhưng tôi đã làm. Tôi thường sử dụng .ToList() ở nơi khác, nhưng thường không phải trong trường hợp này.Làm cách nào để biết liệu một số IE2umercó thể thực hiện được hoãn lại không?

Mã này chứng minh rằng ngay cả một đơn giản 'Chọn' là tùy thuộc vào thực hiện chậm:

var random = new Random(); 
var animals = new[] { "cat", "dog", "mouse" }; 
var randomNumberOfAnimals = animals.Select(x => Math.Floor(random.NextDouble() * 100) + " " + x + "s"); 

foreach (var i in randomNumberOfAnimals) 
{ 
    testContextInstance.WriteLine("There are " + i); 
} 

foreach (var i in randomNumberOfAnimals) 
{ 
    testContextInstance.WriteLine("And now, there are " + i); 
} 

này kết quả đầu ra như sau (các chức năng ngẫu nhiên được gọi mỗi khi bộ sưu tập được lặp thông qua):

There are 75 cats 
There are 28 dogs 
There are 62 mouses 
And now, there are 78 cats 
And now, there are 69 dogs 
And now, there are 43 mouses 

Tôi có nhiều nơi tôi có số IEnumerable<T> làm thành viên của một lớp học. Thường thì kết quả của truy vấn LINQ được gán cho một số IEnumerable<T>. Thông thường đối với tôi điều này không gây ra vấn đề, nhưng gần đây tôi đã tìm thấy một vài nơi trong mã của tôi, nơi nó đặt ra nhiều hơn là một vấn đề hiệu suất.

Khi cố gắng kiểm tra những nơi mà tôi đã mắc phải sai lầm này, tôi nghĩ tôi có thể kiểm tra xem một số IEnumerable<T> cụ thể có thuộc loại IQueryable hay không. Điều này tôi nghĩ sẽ cho tôi biết nếu bộ sưu tập được 'trì hoãn' hay không. Nó chỉ ra rằng điều tra viên tạo ra bởi các nhà điều hành lựa chọn ở trên là loại System.Linq.Enumerable+WhereSelectArrayIterator``[System.String,System.String] và không IQueryable.

Tôi đã sử dụng Reflector để xem giao diện này được kế thừa từ đâu và hóa ra không kế thừa từ bất kỳ thứ gì cho biết đó là 'LINQ' - vì vậy không có cách nào để kiểm tra dựa trên loại bộ sưu tập.

Tôi khá vui khi đặt .ToArray() ở mọi nơi, nhưng tôi muốn có một cơ chế để đảm bảo vấn đề này không xảy ra trong tương lai. Visual Studio dường như biết làm thế nào để làm điều đó bởi vì nó đưa ra một thông báo về 'mở rộng xem kết quả sẽ đánh giá bộ sưu tập.'

Điều tốt nhất tôi đã đưa ra là:

bool deferred = !object.ReferenceEquals(randomNumberOfAnimals.First(), 
             randomNumberOfAnimals.First()); 

Edit: chỉ này hoạt động nếu một đối tượng mới được tạo ra với 'Chọn' và nó không phải là một giải pháp chung. Tôi không khuyến khích nó trong mọi trường hợp mặc dù! Đó là một lưỡi nhỏ trong má của một giải pháp.

+11

Hãy nhớ rằng, biểu thức truy vấn cung cấp cho bạn một đối tượng đại diện cho * chính truy vấn *. Đối tượng không đại diện cho các kết quả của truy vấn, đối tượng đại diện cho một truy vấn. Hãy coi nó như một chuỗi truy vấn SQL, chỉ thông minh hơn. Bạn yêu cầu truy vấn cho kết quả của nó và truy vấn thực thi. Bạn hỏi lại lần nữa, truy vấn sẽ thực thi lại; không đảm bảo rằng kết quả sẽ giống như lần thứ hai bạn hỏi; thế giới có thể đã thay đổi kể từ đó. –

+0

Điều tốt nhất tôi đã gặp phải là [ở đây] (http://stackoverflow.com/questions/4621561/is-there-a-way-to-memorize-or-materialize-an-ienumerable), không dễ dàng mặc dù .. ! – nawfal

+0

Tại sao bạn quan tâm đến việc thực thi hoãn lại? Bạn không nên chú ý đến điều đó, và coi nó là một chi tiết thực hiện riêng tư của bạn 'IEnumerable '. –

Trả lời

13

Thực hiện chậm LINQ đã bị mắc kẹt rất nhiều người, bạn không đơn độc.

Phương pháp tôi đã thực hiện để tránh vấn đề này là như sau:

Các tham số để phương pháp - sử dụng IEnumerable<T> trừ khi có một nhu cầu cho một giao diện cụ thể hơn.

Biến cục bộ - thường là tại thời điểm tôi tạo LINQ, vì vậy tôi sẽ biết liệu có thể đánh giá lười biếng hay không.

Thành viên nhóm - không bao giờ sử dụng IEnumerable<T>, luôn sử dụng List<T>. Và luôn đặt chúng ở chế độ riêng tư.

Thuộc tính - sử dụng IEnumerable<T> và chuyển đổi để lưu trữ trong trình cài đặt.

public IEnumerable<Person> People 
{ 
    get { return people; } 
    set { people = value.ToList(); } 
} 
private List<People> people; 

Mặc dù có trường hợp lý thuyết mà cách tiếp cận này sẽ không hoạt động, tôi chưa chạy vào, và tôi đã nhiệt tình sử dụng các phương pháp mở rộng LINQ kể từ cuối Beta.

BTW: Tôi rất tò mò tại sao bạn sử dụng ToArray(); thay vì ToList(); - với tôi, danh sách có API đẹp hơn nhiều và hầu như không có chi phí hiệu suất.

Cập nhật: Một vài người nhận xét đã chỉ ra đúng rằng mảng có lợi thế về mặt lý thuyết, vì vậy tôi đã sửa đổi tuyên bố ở trên thành "... có (hầu như) không có chi phí hiệu suất".

Cập nhật 2: Tôi đã viết một số mã để thực hiện một số điểm chuẩn vi mô về sự khác biệt về hiệu suất giữa Mảng và Danh sách. Trên máy tính xách tay của tôi, và trong tiêu chuẩn cụ thể của tôi, sự khác biệt là khoảng 5ns (đó là nano giây) mỗi lần truy cập. Tôi đoán có những trường hợp tiết kiệm 5ns trên mỗi vòng lặp sẽ là đáng giá ... nhưng tôi chưa bao giờ gặp một. Tôi phải tăng thử nghiệm của mình lên tới 100 triệu lần lặp trước khi thời gian chạy trở nên đủ dài để đo chính xác.

+0

Bevan: Tôi đồng ý với hầu hết quan điểm của bạn - nhưng câu cuối cùng. Có chi phí hiệu suất cho các danh sách, chúng chỉ rất, rất tối thiểu. Tôi đồng ý, trong gần như tất cả các trường hợp, nó không đáng kể, nhưng Danh sách IS (kính hiển vi) chậm hơn một mảng, vì vậy nếu bạn đang làm cao perf. mã, có một lý do để sử dụng ToArray() đôi khi. –

+0

@Bevan: Một trường hợp nhỏ mà mảng được gọn gàng là JIT cơ bản có thể nhận được hiệu suất tương tự từ chúng như là JIT tối ưu hóa có thể nhận được từ 'Danh sách '. Có các chỉ dẫn IL trực tiếp để làm việc với các mảng - không yêu cầu phương thức nội tuyến riêng biệt. –

+0

bất kỳ lý do cụ thể nào không sử dụng ICollection cho các thành viên của lớp học hoặc IList . Tôi đang hướng tới ICollection cho các thành viên trong lớp –

1

Thông báo về việc mở rộng chế độ xem kết quả sẽ đánh giá bộ sưu tập là thông báo chuẩn được hiển thị cho tất cả các đối tượng IEnumerable. Tôi không chắc chắn rằng có bất kỳ phương tiện không rõ ràng nào để kiểm tra xem có hoãn lại IEnumerable hay không, chủ yếu là do ngay cả một số yield được hoãn lại. Các phương tiện duy nhất hoàn toàn đảm bảo rằng phương thức này không được hoãn lại là chấp nhận ICollection hoặc IList<T>.

+1

Ngay cả một 'IList ' có thể được hoãn lại nếu nó có bộ thu 'Item' ảo. –

+0

Vâng, những gì nhầm lẫn Chevy/Nissan lai nói. Nếu bạn muốn chắc chắn, chỉ chấp nhận một mảng cụ thể. –

+0

@Jeffrey: được cung cấp bởi Chevy: http://www.280z28.org/images/z/IMG_2020.jpg –

1

Hoàn toàn có thể thực hiện thủ công một cách lười biếng IEnumerator<T>, vì vậy không có cách "hoàn toàn chung" để thực hiện. Điều tôi lưu ý là: nếu tôi thay đổi mọi thứ trong danh sách khi liệt kê một số thứ liên quan đến nó, hãy luôn gọi ToArray() trước số foreach.

6

Nói chung, tôi muốn nói bạn nên cố gắng tránh lo lắng về việc liệu quảng cáo có được hoãn lại hay không.

Có những lợi thế đối với bản chất thực thi trực tuyến của IEnumerable<T>.Đó là sự thật - có những lúc nó bất lợi, nhưng tôi khuyên bạn nên luôn luôn xử lý những lần (hiếm) cụ thể - hoặc đi ToList() hoặc ToArray() để chuyển đổi nó thành danh sách hoặc mảng nếu thích hợp.

Phần còn lại của thời gian, tốt hơn là chỉ để cho nó được trì hoãn. Cần phải thường xuyên kiểm tra điều này có vẻ giống như một vấn đề thiết kế lớn hơn ...

+0

đồng ý. câu hỏi càng trở nên khơi dậy bởi sự tò mò để biết cách làm điều này, và cũng để tìm những nơi xảy ra vấn đề này. tôi chắc chắn muốn khá nhiều luôn làm 'ToArray()'.tôi đã chỉ nhận được đi mà không có nó bởi vì a) nó là hơi xấu xí để xem xét và b) tôi nghĩ rằng một 'Chọn' chiếu đã ngay lập tức nhận ra. –

1

Đây là một phản ứng thú vị để trì hoãn thực thi - hầu hết mọi người xem nó là tích cực ở chỗ nó cho phép bạn chuyển đổi luồng dữ liệu mà không cần phải đệm mọi thứ.

Thử nghiệm được đề xuất của bạn sẽ không hoạt động, vì không có lý do gì khiến một phương thức trình lặp không thể tạo cùng một đối tượng tham chiếu giống như đối tượng đầu tiên của nó trong hai lần thử liên tiếp.

IEnumerable<string> Names() 
{ 
    yield return "Fred"; 
} 

Điều đó sẽ trả về cùng một đối tượng chuỗi tĩnh mỗi lần, làm mục duy nhất trong chuỗi.

Như bạn không thể chắc chắn phát hiện các lớp biên dịch tạo được trả về từ một phương pháp lặp, bạn sẽ phải làm điều ngược lại: kiểm tra một vài container nổi tiếng:

public static IEnumerable<T> ToNonDeferred(this IEnumerable<T> source) 
{ 
    if (source is List<T> || source is T[]) // and any others you encounter 
     return source; 

    return source.ToArray(); 
} 

Bằng trở về IEnumerable<T>, chúng tôi giữ bộ sưu tập chỉ đọc, điều này quan trọng vì chúng tôi có thể lấy lại bản sao hoặc bản gốc.

+0

vì vậy đây là cách studio trực quan làm điều đó? –

+1

Visual Studio cho thấy "mở rộng chế độ xem kết quả sẽ đánh giá bộ sưu tập" cho _any_ có thể đếm được mà không phải là 'ICollection'. –

+1

và i LOVE hoãn thực thi. tôi vừa tạo ra một giả thiết ngu ngốc (và thậm chí không phải là một ý thức) rằng Select (x => x + "foo") sẽ không được hoãn lại. tôi hoàn toàn hiểu tại sao nó, nhưng nó đã không bao giờ kết quả [đáng ngạc nhiên] trong một lỗi trong mã của tôi cho đến ngày hôm nay –

3

Năm xu của tôi. Khá thường xuyên bạn phải đối phó với một enumerable rằng bạn không có ý tưởng những gì bên trong của nó.

lựa chọn của bạn là:

  • lượt nó vào một danh sách trước khi sử dụng nó, nhưng rất có thể là nó vô tận bạn đang gặp rắc rối
  • sử dụng nó như là và bạn có khả năng phải đối mặt với tất cả các loại thực hiện chậm hài hước thứ và bạn đang gặp rắc rối một lần nữa

Dưới đây là một ví dụ:

[TestClass] 
public class BadExample 
{ 
    public class Item 
    { 
     public String Value { get; set; } 
    } 
    public IEnumerable<Item> SomebodysElseMethodWeHaveNoControlOver() 
    { 
     var values = "at the end everything must be in upper".Split(' '); 
     return values.Select(x => new Item { Value = x }); 
    } 
    [TestMethod] 
    public void Test() 
    { 
     var items = this.SomebodysElseMethodWeHaveNoControlOver(); 
     foreach (var item in items) 
     { 
      item.Value = item.Value.ToUpper(); 
     } 
     var mustBeInUpper = String.Join(" ", items.Select(x => x.Value).ToArray()); 
     Trace.WriteLine(mustBeInUpper); // output is in lower: at the end everything must be in upper 
     Assert.AreEqual("AT THE END EVERYTHING MUST BE IN UPPER", mustBeInUpper); // <== fails here 
    } 
} 

Vì vậy, không có cách nào để có được đi với nó, nhưng một: iterate nó chính xác một thời gian trên as-you-go cơ sở.

Đó rõ ràng là một lựa chọn thiết kế tồi để sử dụng cùng một giao diện IEnumerable cho các tình huống thực thi ngay lập tức và trì hoãn. Phải có một sự phân biệt rõ ràng giữa hai điều này, để nó rõ ràng từ tên hoặc bằng cách kiểm tra một tài sản cho dù số đếm được hoãn lại hay không.

Gợi ý: Trong mã của bạn, hãy xem xét sử dụng IReadOnlyCollection<T> thay cho số IEnumerable<T> đồng bằng, ngoài việc bạn nhận được thuộc tính Count. Bằng cách này bạn biết chắc chắn nó không phải là vô tận và bạn có thể biến nó thành một danh sách không có vấn đề gì.

+1

nhờ @bonomo. bạn có thể đăng những gì kết quả ở đây là dành cho những người không có thời gian để chạy nó :-) cảm ơn –

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