Một cách sử dụng thực tế của từ khóa năng suất đỉnh điểm trong C # [đã đóng]


76

Sau gần 4 năm kinh nghiệm, tôi chưa thấy một mã nào sử dụng từ khóa suất . Ai đó có thể chỉ cho tôi cách sử dụng thực tế (cùng với lời giải thích) của từ khóa này không, và nếu vậy, không có cách nào khác dễ dàng hơn để điền vào những gì nó có thể làm?


9
Tất cả (hoặc ít nhất là nhiều nhất) LINQ được triển khai bằng năng suất. Ngoài ra khung công tác Unity3D đã tìm thấy một số sử dụng tốt cho nó - nó được sử dụng để tạm dừng các chức năng (trên các báo cáo lợi tức) và tiếp tục sử dụng nó sau khi sử dụng trạng thái trong IEnumerable.
Dani

2
Không nên chuyển nó sang StackOverflow?
Daniel Varod

4
@Danny - Nó không phù hợp với Stack Overflow, vì câu hỏi không phải là giải quyết một vấn đề cụ thể mà là hỏi về những gì yieldcó thể được sử dụng nói chung.
ChrisF

9
Cho thật? Tôi không thể nghĩ về một ứng dụng duy nhất mà tôi chưa sử dụng nó.
Aaronaught

Câu trả lời:


107

Hiệu quả

Các yieldtừ khóa có hiệu quả tạo ra một liệt kê lười biếng trên mục bộ sưu tập có thể được hiệu quả hơn. Ví dụ: nếu foreachvòng lặp của bạn lặp lại chỉ trong 5 mục đầu tiên của 1 triệu mục thì đó là tất cả yieldlợi nhuận và bạn đã không xây dựng bộ sưu tập 1 triệu mục trong nội bộ trước. Tương tự như vậy, bạn sẽ muốn sử dụng yieldvới IEnumerable<T>các giá trị trả về trong các kịch bản lập trình của riêng bạn để đạt được hiệu quả tương tự.

Ví dụ về hiệu quả đạt được trong một kịch bản nhất định

Không phải là một phương pháp lặp, tiềm năng sử dụng không hiệu quả của một bộ sưu tập lớn,
(Bộ sưu tập trung gian được xây dựng có rất nhiều mục)

// Method returns all million items before anything can loop over them. 
List<object> GetAllItems() {
    List<object> millionCustomers;
    database.LoadMillionCustomerRecords(millionCustomers); 
    return millionCustomers;
}

// MAIN example ---------------------
// Caller code sample:
int num = 0;
foreach(var itm in GetAllItems())  {
    num++;
    if (num == 5)
        break;
}
// Note: One million items returned, but only 5 used. 

Phiên bản lặp, hiệu quả
(Không có bộ sưu tập trung gian được xây dựng)

// Yields items one at a time as the caller's foreach loop requests them
IEnumerable<object> IterateOverItems() {
    for (int i; i < database.Customers.Count(); ++i)
        yield return database.Customers[i];
}

// MAIN example ---------------------
// Caller code sample:
int num = 0;
foreach(var itm in IterateOverItems())  {
    num++;
    if (num == 5)
        break;
}
// Note: Only 5 items were yielded and used out of the million.

Đơn giản hóa một số kịch bản lập trình

Trong một trường hợp khác, nó làm cho một số loại sắp xếp và hợp nhất các danh sách trở nên dễ dàng hơn để lập trình bởi vì bạn chỉ cần yieldsắp xếp lại theo thứ tự mong muốn thay vì sắp xếp chúng vào một bộ sưu tập trung gian và hoán đổi chúng trong đó. Có nhiều kịch bản như vậy.

Chỉ cần một ví dụ là hợp nhất hai danh sách:

IEnumerable<object> EfficientMerge(List<object> list1, List<object> list2) {
    foreach(var o in list1) 
        yield return o; 
    foreach(var o in list2) 
        yield return o;
}

Phương pháp này mang lại một danh sách các mục liên tục, thực sự là một sự hợp nhất không cần bộ sưu tập trung gian.

Thêm thông tin

Các yieldtừ khóa chỉ có thể được sử dụng trong bối cảnh của một phương pháp lặp (có một kiểu trả về của IEnumerable, IEnumerator, IEnumerable<T>, hoặc IEnumerator<T>.) Và có một mối quan hệ đặc biệt với foreach. Lặp đi lặp lại là phương pháp đặc biệt. Các tài liệu năng suất MSDNiterator tài liệu chứa rất nhiều thông tin thú vị và giải thích các khái niệm. Hãy chắc chắn để tương quan với các foreachtừ khóa bằng cách đọc về nó quá, để bổ sung hiểu biết của bạn lặp.

Để tìm hiểu về cách các trình vòng lặp đạt được hiệu quả của chúng, bí mật nằm ở mã IL được tạo bởi trình biên dịch C #. IL được tạo cho một phương thức iterator khác rất nhiều so với phương thức được tạo cho một phương thức (không lặp) thông thường. Bài viết này (Từ khóa năng suất thực sự tạo ra cái gì?) Cung cấp loại hiểu biết đó.


2
Chúng đặc biệt hữu ích cho các thuật toán lấy một chuỗi (có thể dài) và tạo ra một thuật toán khác trong đó ánh xạ không phải là một. Một ví dụ về điều này là cắt đa giác; bất kỳ cạnh cụ thể nào cũng có thể tạo ra nhiều hoặc thậm chí không có cạnh nào khi được cắt. Trình vòng lặp làm cho điều này trở nên dễ dàng hơn rất nhiều để diễn đạt, và năng suất là một trong những cách tốt nhất để viết chúng.
Donal Fellows

+1 Câu trả lời tốt hơn nhiều như tôi đã viết ra. Bây giờ tôi cũng học được năng suất là tốt cho hiệu suất tốt hơn.
Jan_V

3
Ngày xửa ngày xưa, tôi đã sử dụng năng suất để xây dựng các gói cho giao thức mạng nhị phân. Có vẻ như sự lựa chọn tự nhiên nhất trong C #.
Gyorgy Andottok

4
Không database.Customers.Count()liệt kê toàn bộ bảng liệt kê của khách hàng, do đó yêu cầu mã hiệu quả hơn để đi qua mọi mục?
Stephen

5
Gọi tôi là hậu môn, nhưng đó là nối, không hợp nhất. (Và linq đã có phương thức Concat.)
OldFart

4

Cách đây một thời gian, tôi có một ví dụ thực tế, giả sử bạn có một tình huống như thế này:

List<Button> buttons = new List<Button>();
void AddButtons()
{
   for ( int i = 0; i <= 10; i++ ) {
      var button = new Button();
      buttons.Add(button);
      button.Click += (sender, e) => 
          MessageBox.Show(String.Format("You clicked button number {0}", ???));
   }
}

Đối tượng nút không biết vị trí của mình trong bộ sưu tập. Giới hạn tương tự áp dụng cho Dictionary<T>hoặc các loại bộ sưu tập khác.

Đây là giải pháp của tôi bằng cách sử dụng yieldtừ khóa:

interface IHasId { int Id { get; set; } }

class IndexerList<T>: List<T>, IEnumerable<T> where T: IHasId
{
   List<T> elements = new List<T>();
   new public void Clear() { elements.Clear(); }
   new public void Add(T element) { elements.Add(element); }
   new public int Count { get { return elements.Count; } }    
   new public IEnumerator<T> GetEnumerator()
   {
      foreach ( T c in elements )
         yield return c;
   }

   new public T this[int index]
   {
      get
      {
         foreach ( T c in elements ) {
            if ( (int)c.Id == index )
               return c;
         }
         return default(T);
      }
   }
}

Và đó là cách tôi sử dụng nó:

class ButtonWithId: Button, IHasId
{
   public int Id { get; private set; }
   public ButtonWithId(int id) { this.Id = id; }
}

IndexerList<ButtonWithId> buttons = new IndexerList<ButtonWithId>();
void AddButtons()
{
   for ( int i = 10; i <= 20; i++ ) {
      var button = new ButtonWithId(i);
      buttons.Add(button);
      button.Click += (sender, e) => 
         MessageBox.Show(String.Format("You clicked button number {0}", ( (ButtonWithId)sender ).Id));
   }
}

Tôi không phải thực hiện một forvòng lặp trên bộ sưu tập của mình để tìm chỉ mục. Nút của tôi có ID và cái này cũng được sử dụng làm chỉ mục IndexerList<T>, vì vậy bạn tránh mọi chỉ số hoặc chỉ số ID dư thừa - đó là điều tôi thích! Chỉ số / Id có thể là một số tùy ý.


2

Một ví dụ thực tế có thể được tìm thấy ở đây:

http://www.ytechie.com/2009/02/USE-c-yield-for-readability-and-performance.html

Có một số lợi thế của việc sử dụng năng suất so với mã tiêu chuẩn:

  • Nếu iterator được sử dụng để xây dựng một danh sách thì bạn có thể mang lại lợi nhuận và người gọi có thể quyết định liệu anh ta có muốn kết quả đó trong danh sách hay không.
  • Người gọi cũng có thể quyết định hủy bỏ việc lặp lại vì một lý do nằm ngoài phạm vi của những gì bạn đang làm trong lần lặp.
  • Mã ngắn hơn một chút.

Tuy nhiên, như Jan_V đã nói (chỉ cần đánh bại tôi trong vài giây :-) bạn có thể sống mà không cần nó bởi vì bên trong trình biên dịch sẽ tạo mã gần như giống hệt nhau trong cả hai trường hợp.


1

Đây là một ví dụ:

https://bitbucket.org/ant512/usinessweek/src/a745d02ba16f/source/WorkingWeek/Week.cs#cl-158

Lớp học thực hiện tính toán ngày dựa trên một tuần làm việc. Tôi có thể nói với một ví dụ của lớp rằng Bob làm việc 9:30 đến 17:30 mỗi ngày trong tuần với một giờ nghỉ trưa vào lúc 12:30. Với kiến ​​thức này, hàm AsceinatingShifts () sẽ mang lại các đối tượng dịch chuyển làm việc giữa các ngày được cung cấp. Để liệt kê tất cả các ca làm việc của Bob trong khoảng thời gian từ ngày 1 tháng 1 đến ngày 1 tháng 2 năm nay, bạn sẽ sử dụng nó như thế này:

foreach (var shift in week.AscendingShifts(new DateTime(2011, 1, 1), new DateTime(2011, 2, 1)) {
    Console.WriteLine(shift);
}

Lớp học không thực sự lặp lại trong một bộ sưu tập. Tuy nhiên, sự thay đổi giữa hai ngày có thể được coi là một bộ sưu tập. Các yieldnhà điều hành làm cho nó có thể để lặp qua bộ sưu tập tưởng tượng này mà không cần tạo bộ sưu tập riêng của mình.


1

Tôi có một lớp dữ liệu db nhỏ có một commandlớp trong đó bạn đặt văn bản lệnh SQL, loại lệnh và trả về một 'tham số lệnh' của IE.

Về cơ bản, ý tưởng là đã gõ các lệnh CLR thay vì điền SqlCommandcác thuộc tính và tham số thủ công mọi lúc.

Vì vậy, có một chức năng trông như thế này:

IEnumerable<DbParameter> GetParameters()
{
    // here i do something like

    yield return new DbParameter { name = "@Age", value = this.Age };

    yield return new DbParameter { name = "@Name", value = this.Name };
}

Lớp kế thừa commandlớp này có các thuộc tính AgeName.

Sau đó, bạn có thể tạo một commandđối tượng chứa đầy các thuộc tính của nó và chuyển nó đến một dbgiao diện thực sự thực hiện lệnh gọi.

Tất cả trong tất cả làm cho nó thực sự dễ dàng để làm việc với các lệnh SQL và giữ chúng được gõ.


1

Mặc dù trường hợp sáp nhập đã được đề cập trong câu trả lời được chấp nhận, hãy để tôi chỉ cho bạn phương pháp mở rộng thông số hợp nhất năng suất ™:

public static IEnumerable<T> AppendParams<T>(this IEnumerable<T> a, params T[] b)
{
    foreach (var el in a) yield return el;
    foreach (var el in b) yield return el;
}

Tôi sử dụng điều này để xây dựng các gói của một giao thức mạng:

static byte[] MakeCommandPacket(string cmd)
{
    return
        header
        .AppendParams<byte>(0, 0, 1, 0, 0, 1, 0x92, 0, 0, 0, 0)
        .AppendAscii(cmd)
        .MarkLength()
        .MarkChecksum()
        .ToArray();
}

Các MarkChecksumphương pháp, ví dụ, trông như thế này. Và nó cũng có một yield:

public static IEnumerable<byte> MarkChecksum(this IEnumerable<byte> data, int pos = 6)
{
    foreach (byte b in data)
    {
        yield return pos-- == 0 ? (byte)data.Sum(z => z) : b;
    }
}

Nhưng hãy cẩn thận khi sử dụng các phương thức tổng hợp như Sum () trong phương pháp liệt kê vì chúng kích hoạt một quy trình liệt kê riêng.


1

Ví dụ đàn hồi Tìm kiếm .NET repo có một ví dụ tuyệt vời về việc sử dụng yield returnđể phân vùng bộ sưu tập thành nhiều bộ sưu tập với kích thước được chỉ định:

https://github.com/elastic/elaticsearch-net-example/blob/master/src/NuSearch.Domain/Extensions/PartitionExtension.cs

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int size)
    {
        T[] array = null;
        int count = 0;
        foreach (T item in source)
        {
            if (array == null)
            {
                array = new T[size];
            }
            array[count] = item;
            count++;
            if (count == size)
            {
                yield return new ReadOnlyCollection<T>(array);
                array = null;
                count = 0;
            }
        }
        if (array != null)
        {
            Array.Resize(ref array, count);
            yield return new ReadOnlyCollection<T>(array);
        }
    }

0

Mở rộng theo câu trả lời của Jan_V, tôi vừa gặp một trường hợp thực tế liên quan đến nó:

Tôi cần sử dụng các phiên bản Kernel32 của FindFirstFile / FindNextFile. Bạn có thể xử lý từ cuộc gọi đầu tiên và cung cấp cho tất cả các cuộc gọi tiếp theo. Gói cái này trong một liệt kê và bạn nhận được một cái gì đó bạn có thể sử dụng trực tiếp với foreach.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.