2008-11-25 13 views
26

Khi nào tôi nghĩ mình có thể sử dụng từ khóa lợi nhuận, tôi lùi lại một bước và xem nó sẽ tác động như thế nào đến dự án của tôi. Tôi luôn luôn trở lại một bộ sưu tập thay vì yeilding bởi vì tôi cảm thấy phí tổn của việc duy trì trạng thái của phương pháp yeilding không mua cho tôi nhiều. Trong hầu hết các trường hợp tôi trả lại bộ sưu tập, tôi cảm thấy rằng 90% thời gian, phương thức gọi sẽ lặp lại trên tất cả các phần tử trong bộ sưu tập hoặc sẽ tìm kiếm một loạt các phần tử trong toàn bộ bộ sưu tập.Năng suất có hữu ích bên ngoài LINQ không?

Tôi hiểu tính hữu ích của nó trong LINQ, nhưng tôi cảm thấy rằng chỉ có nhóm LINQ đang viết các đối tượng có khả năng phức tạp như vậy mà năng suất là hữu ích.

Có ai đã viết bất kỳ thứ gì như hoặc không thích linq ở đó sản lượng có hữu ích không?

+0

Ý của bạn là bên ngoài của LINQ, hoặc IEnumerable? Tôi nên hình dung rằng việc sử dụng năng suất khác với các điều tra viên sẽ khá hiếm (và thú vị). Jon Skeet đề cập đến một trong cuốn sách của mình ... – Benjol

+0

Tôi vẫn còn hoài nghi. – Greg

+0

sử dụng rất thú vị của sản lượng là trong thư viện [Threading Power Threading Library của Jeffrey Richter] (http://msdn.microsoft.com/en-us/magazine/cc546608.aspx) – Yurec

Trả lời

12

Gần đây tôi đã phải trình bày các biểu thức toán học dưới dạng một lớp biểu thức. Khi đánh giá biểu thức, tôi phải đi qua cấu trúc cây với một cây con sau khi đặt hàng. Để đạt được điều này tôi thực hiện IEnumerable <T> như thế này:

public IEnumerator<Expression<T>> GetEnumerator() 
{ 
    if (IsLeaf) 
    { 
     yield return this; 
    } 
    else 
    { 
     foreach (Expression<T> expr in LeftExpression) 
     { 
      yield return expr; 
     } 
     foreach (Expression<T> expr in RightExpression) 
     { 
      yield return expr; 
     } 
     yield return this; 
    } 
} 

Sau đó, tôi chỉ đơn giản là có thể sử dụng một foreach phải đi qua sự biểu hiện.Bạn cũng có thể thêm thuộc tính để thay đổi thuật toán truyền tải khi cần.

+1

C# thực sự cần từ khóa thu nhập để trừu tượng ra vòng lặp foreach (x trong bộ sưu tập) {yield x} mà mọi người viết 100x một ngày trong những ngày này :-( –

+3

nếu bạn đang làm foreach (x trong bộ sưu tập) {yield return x;} ... bạn chỉ có thể làm. Chọn (x => x) nếu bạn muốn làm việc chống lại một tập hợp các mục trong một bộ sưu tập, bạn có thể làm cho một phương pháp mở rộng .Foreach (IEnumerable col, Hành động hành động) –

27

Lưu ý rằng với lợi nhuận, bạn đang lặp lại quá trình thu thập một lần, nhưng khi bạn tạo danh sách, bạn sẽ lặp lại qua hai lần.

Lấy ví dụ, một iterator lọc:

IEnumerator<T> Filter(this IEnumerator<T> coll, Func<T, bool> func) 
{ 
    foreach(T t in coll) 
     if (func(t)) yield return t; 
} 

Bây giờ, bạn có thể chuỗi này:

MyColl.Filter(x=> x.id > 100).Filter(x => x.val < 200).Filter (etc) 

Bạn phương pháp sẽ được tạo ra (và tung) ba danh sách. Phương pháp của tôi lặp lại nó chỉ một lần.

Ngoài ra, khi bạn trả lại bộ sưu tập, bạn đang buộc triển khai cụ thể cho người dùng của mình. Trình lặp là tổng quát hơn.

+0

Không nên đọc: if (func (t))) lợi nhuận t; –

+2

Ya'know ... Nếu không có Intellisense, tôi chỉ không thể lập trình ..... –

+2

:) Tôi đã có một dự án VS2008 không chứa gì ngoài đoạn mã SO ... – GalacticCowboy

1

Cá nhân, tôi không thấy tôi đang sử dụng năng suất trong chương trình hàng ngày bình thường của mình. Tuy nhiên, gần đây tôi đã bắt đầu chơi với các mẫu Robotics Studio và thấy rằng sản lượng được sử dụng rộng rãi ở đó, vì vậy tôi cũng thấy nó được sử dụng kết hợp với CCR (Concurrency và Coordination Runtime), nơi bạn có vấn đề về đồng bộ và đồng thời.

Dù sao, vẫn cố gắng để có được đầu của tôi xung quanh nó là tốt.

1

Lợi nhuận rất hữu ích vì nó giúp bạn tiết kiệm không gian. Hầu hết các tối ưu hóa trong lập trình làm cho một thương mại ra giữa không gian (đĩa, bộ nhớ, mạng) và chế biến. Năng suất như một cấu trúc lập trình cho phép bạn lặp qua một bộ sưu tập nhiều lần theo trình tự mà không cần một bản sao riêng của bộ sưu tập cho mỗi lần lặp.

xem xét ví dụ sau:

static IEnumerable<Person> GetAllPeople() 
{ 
    return new List<Person>() 
    { 
     new Person() { Name = "George", Surname = "Bush", City = "Washington" }, 
     new Person() { Name = "Abraham", Surname = "Lincoln", City = "Washington" }, 
     new Person() { Name = "Joe", Surname = "Average", City = "New York" } 
    }; 
} 

static IEnumerable<Person> GetPeopleFrom(this IEnumerable<Person> people, string where) 
{ 
    foreach (var person in people) 
    { 
     if (person.City == where) yield return person; 
    } 
    yield break; 
} 

static IEnumerable<Person> GetPeopleWithInitial(this IEnumerable<Person> people, string initial) 
{ 
    foreach (var person in people) 
    { 
     if (person.Name.StartsWith(initial)) yield return person; 
    } 
    yield break; 
} 

static void Main(string[] args) 
{ 
    var people = GetAllPeople(); 
    foreach (var p in people.GetPeopleFrom("Washington")) 
    { 
     // do something with washingtonites 
    } 

    foreach (var p in people.GetPeopleWithInitial("G")) 
    { 
     // do something with people with initial G 
    } 

    foreach (var p in people.GetPeopleWithInitial("P").GetPeopleFrom("New York")) 
    { 
     // etc 
    } 
} 

(Rõ ràng là bạn không cần phải sử dụng năng suất với các phương pháp mở rộng, nó chỉ tạo ra một mô hình mạnh mẽ để suy nghĩ về dữ liệu.)

Như bạn có thể thấy, nếu bạn có rất nhiều phương pháp "lọc" (nhưng nó có thể là bất kỳ loại phương pháp nào hoạt động trên danh sách mọi người), bạn có thể kết nối nhiều người với nhau mà không cần thêm dung lượng lưu trữ cho mỗi bước. Đây là một cách để nâng cao ngôn ngữ lập trình (C#) lên để thể hiện các giải pháp của bạn tốt hơn.

Tác dụng phụ đầu tiên của sản lượng là nó trì hoãn việc thực thi logic lọc cho đến khi bạn thực sự yêu cầu nó. Do đó, nếu bạn tạo một biến loại IEnumerable <> (với sản lượng) nhưng không bao giờ lặp qua nó, bạn không bao giờ thực thi logic hoặc tiêu thụ không gian vốn là một tối ưu hóa mạnh mẽ và miễn phí.

Tác dụng phụ khác là năng suất hoạt động trên giao diện thu thập chung thấp nhất (IEnumerable <>) cho phép tạo mã giống như thư viện với khả năng ứng dụng rộng.

+0

Tất cả những thứ đó thực sự * chỉ là LINQ. Nếu bạn đang sử dụng .NET 3.5, bạn chắc chắn sẽ thực hiện GetPeopleWithInitial bằng cách trả về people.Where (person => person.Name.StartsWith (ban đầu)). –

+0

tốt, có và không. Những gì bạn đang nói là đúng, nhưng bạn sẽ phải person => person.Name.Startswith() ở mọi nơi. Với một phương pháp thư viện bạn có được những lợi ích rõ ràng ... năng suất cũng có trong .NET 2 trong khi không phải ai cũng có .NET 3.5 ... –

+0

Pieter: Tôi không nói bạn nên loại bỏ các phương thức thư viện, nhưng tôi thường triển khai chúng bằng LINQ. Và khi nó gần như là LINQ, nó không thực sự cảm thấy giống như một câu trả lời cho "khi năng suất hữu ích bên ngoài LINQ" - reimplementing LINQ mình không đếm, IMO :) –

2

Bất cứ khi nào hàm của bạn trả về IEnumerable, bạn nên sử dụng "năng suất". Không chỉ trong .Net> 3.0.

Net 2.0 Ví dụ:

public static class FuncUtils 
    { 
     public delegate T Func<T>(); 
     public delegate T Func<A0, T>(A0 arg0); 
     public delegate T Func<A0, A1, T>(A0 arg0, A1 arg1); 
     ... 

     public static IEnumerable<T> Filter<T>(IEnumerable<T> e, Func<T, bool> filterFunc) 
     { 
      foreach (T el in e) 
       if (filterFunc(el)) 
        yield return el; 
     } 


     public static IEnumerable<R> Map<T, R>(IEnumerable<T> e, Func<T, R> mapFunc) 
     { 
      foreach (T el in e) 
       yield return mapFunc(el); 
     } 
     ... 
11

Tại một công ty trước đây, tôi thấy mình viết vòng như thế này:

for (DateTime date = schedule.StartDate; date <= schedule.EndDate; 
    date = date.AddDays(1)) 

Với một khối iterator rất đơn giản, tôi đã có thể thay đổi điều này để :

foreach (DateTime date in schedule.DateRange) 

Nó làm cho mã dễ đọc hơn rất nhiều, IMO.

+2

Wow - Jon Skeet code Tôi không đồng ý với! = X Từ ví dụ đầu tiên, rõ ràng là bạn đang lặp lại trong nhiều ngày, nhưng sự rõ ràng đó bị thiếu trong lần thứ hai. Tôi muốn sử dụng một cái gì đó như 'schedule.DateRange.Days()' để tránh sự mơ hồ. –

+0

Điều đó đòi hỏi nhiều hơn là chỉ thực hiện một thuộc tính duy nhất, tất nhiên. Tôi muốn nói rằng rõ ràng là một DateRange là một phạm vi ngày, đó là những ngày, nhưng đó là một điều chủ quan. Nó có thể đã được gọi là "Ngày" thay vì DateRange - không chắc chắn. Dù bằng cách nào, nó ít lông tơ hơn bản gốc. –

+0

Vâng, đó là sự thật. * Nhún vai * Tôi sẽ không tự tin với nó, nhưng nếu nó rõ ràng với tác giả và bất cứ người bảo trì nào trong tương lai, thì điều đó không quan trọng. –

2

Tôi không chắc chắn về việc thực hiện C# của năng suất(), nhưng trên ngôn ngữ động, nó hiệu quả hơn nhiều so với việc tạo toàn bộ bộ sưu tập. trên nhiều trường hợp, nó giúp dễ dàng làm việc với bộ dữ liệu lớn hơn nhiều so với RAM.

0

Tôi đã sử dụng năng suất trong mã phi LINQ thứ như thế này (giả sử các chức năng không sống trong cùng một lớp):

public IEnumerable<string> GetData() 
{ 
    foreach(String name in _someInternalDataCollection) 
    { 
     yield return name; 
    } 
} 

... 

public void DoSomething() 
{ 
    foreach(String value in GetData()) 
    { 
     //... Do something with value that doesn't modify _someInternalDataCollection 
    } 
} 

Bạn phải cẩn thận không để vô tình thay đổi bộ sưu tập mà GetData của bạn () chức năng đang lặp qua mặc dù, hoặc nó sẽ ném một ngoại lệ.

8

yield được phát triển cho C# 2 (trước LINQ trong C# 3).

Chúng tôi đã sử dụng nó rất nhiều trong ứng dụng web C# 2 doanh nghiệp lớn khi xử lý truy cập dữ liệu và tính toán lặp lại nhiều lần.

Bộ sưu tập tuyệt vời bất cứ khi nào bạn có một vài yếu tố mà bạn sắp đạt được nhiều lần.

Tuy nhiên trong nhiều trường hợp truy cập dữ liệu, bạn có số lượng lớn các yếu tố mà bạn không nhất thiết phải vượt qua vòng trong một bộ sưu tập lớn.

Đây thực chất là những gì mà SqlDataReader thực hiện - đó chỉ là điều tra viên tùy chỉnh chuyển tiếp.

Điều gì yield cho phép bạn làm là nhanh chóng và với mã tối thiểu viết các điều tra viên tùy chỉnh của riêng bạn.

Mọi thứ yield đều có thể thực hiện được trong C# 1 - nó chỉ lấy các đoạn mã để thực hiện.

LINQ thực sự tối đa hóa giá trị của hành vi lợi nhuận, nhưng chắc chắn đó không phải là ứng dụng duy nhất.

1

Lưu ý rằng năng suất cho phép bạn làm mọi thứ theo cách "lười". Bởi lười biếng, tôi có nghĩa là việc đánh giá các yếu tố tiếp theo trong IEnumberable không được thực hiện cho đến khi phần tử thực sự được yêu cầu. Điều này cho phép bạn sức mạnh để làm một vài điều khác nhau. Một là bạn có thể tạo ra một danh sách dài vô hạn mà không cần phải thực sự tính toán vô hạn. Thứ hai, bạn có thể trả về một liệt kê các ứng dụng hàm. Các hàm sẽ chỉ được áp dụng khi bạn lặp qua danh sách.

0

Năng suất rất hữu ích nói chung. Đó là trong ruby ​​trong số các ngôn ngữ khác có hỗ trợ lập trình phong cách chức năng, do đó, nó giống như nó gắn liền với linq. Đó là nhiều cách khác xung quanh, linq đó là chức năng trong phong cách, vì vậy nó sử dụng năng suất.

Tôi gặp sự cố khi chương trình của tôi đang sử dụng rất nhiều CPU trong một số tác vụ nền. Những gì tôi thực sự muốn là vẫn có thể viết các chức năng như bình thường, để tôi có thể dễ dàng đọc chúng (tức là toàn bộ luồng so với sự kiện dựa trên đối số). Và vẫn có thể phá vỡ các chức năng nếu chúng mất quá nhiều CPU. Năng suất là hoàn hảo cho việc này. Tôi đã viết một blog post về vấn đề này và các nguồn có sẵn cho tất cả để grok :)

18

Tôi hiểu tính hữu dụng của nó trong LINQ, nhưng tôi cảm thấy rằng chỉ có đội LINQ đang viết queriable phức tạp như các đối tượng sản lượng đó là hữu ích.

Lợi nhuận rất hữu ích ngay sau khi được triển khai trong .NET 2.0, vốn đã lâu trước khi có ai đó nghĩ đến LINQ.

Tại sao tôi lại viết chức năng này:

IList<string> LoadStuff() { 
    var ret = new List<string>(); 
    foreach(var x in SomeExternalResource) 
    ret.Add(x); 
    return ret; 
} 

Khi tôi có thể sử dụng năng suất và tiết kiệm công sức và sự phức tạp của việc tạo ra một danh sách tạm thời không có lý do chính đáng:

IEnumerable<string> LoadStuff() { 
    foreach(var x in SomeExternalResource) 
    yield return x; 
} 

Nó cũng có thể có lợi thế hiệu suất rất lớn. Nếu mã của bạn chỉ xảy ra khi sử dụng 5 phần tử đầu tiên của bộ sưu tập, thì việc sử dụng năng suất sẽ thường tránh được nỗ lực tải bất kỳ thứ gì vượt quá điểm đó. Nếu bạn xây dựng một bộ sưu tập sau đó trả lại nó, bạn lãng phí rất nhiều thời gian và không gian tải những thứ bạn sẽ không bao giờ cần.

Tôi có thể tiếp tục và bật ...

+0

Tôi tin rằng Anders Hejlsberg đang làm việc cho Linq vài năm trước. –

2

Tôi là một người hâm mộ năng suất lớn trong C#. Điều này đặc biệt đúng trong các khung công tác gia đình lớn, nơi thường có các phương thức hoặc thuộc tính trả về List là một tập hợp con của một IEnumerable khác. Những lợi ích mà tôi nhìn thấy là:

  • giá trị trả về của một phương pháp sử dụng năng suất là bất biến
  • bạn chỉ lặp qua danh sách một lần
  • nó một biến thực hiện muộn hoặc lười biếng, có nghĩa là mã để trả lại giá trị không được thực hiện cho đến khi cần thiết (mặc dù điều này có thể khiến bạn không biết bạn đang làm gì)
  • thay đổi danh sách nguồn, bạn không phải gọi để nhận một IEnumerable khác, bạn chỉ cần lặp lại qua IEnumeable lần nữa
  • nhiều hơn nữa

Một lợi ích lớn khác của lợi nhuận là khi phương pháp của bạn có khả năng sẽ trả về hàng triệu giá trị. Vì vậy, nhiều người có tiềm năng hết bộ nhớ chỉ cần xây dựng Danh sách trước khi phương thức thậm chí có thể trả lại nó.Với năng suất, phương pháp này chỉ có thể tạo và trả về hàng triệu giá trị, và miễn là người gọi cũng không lưu trữ mọi giá trị. Vì vậy, nó tốt cho các hoạt động xử lý/tổng hợp dữ liệu quy mô lớn

0

Các phần mở rộng System.Linq IEnumerable là rất tốt, nhưng đôi khi bạn muốn nhiều hơn. Ví dụ, hãy xem xét việc gia hạn sau đây:

public static class CollectionSampling 
{ 
    public static IEnumerable<T> Sample<T>(this IEnumerable<T> coll, int max) 
    { 
     var rand = new Random(); 
     using (var enumerator = coll.GetEnumerator()); 
     { 
      while (enumerator.MoveNext()) 
      { 
       yield return enumerator.Current; 
       int currentSample = rand.Next(max); 
       for (int i = 1; i <= currentSample; i++) 
        enumerator.MoveNext(); 
      } 
     } 
    }  
} 

Một ưu điểm thú vị của năng suất là người gọi không thể cast giá trị trả về các loại bộ sưu tập ban đầu và điều chỉnh bộ sưu tập nội bộ của bạn