Penggunaan praktis kata kunci “hasil” di C # [ditutup]

76

Setelah hampir 4 tahun pengalaman, saya belum melihat kode di mana kata kunci hasil digunakan. Adakah yang bisa menunjukkan kepada saya penggunaan praktis (bersama penjelasan) dari kata kunci ini, dan jika demikian, bukankah ada cara lain yang lebih mudah untuk memenuhi apa yang dapat dilakukannya?

Saeed Neamati
sumber
9
Semua (atau paling tidak sebagian besar) LINQ diimplementasikan menggunakan yield. Kerangka kerja Unity3D juga menemukan beberapa kegunaan yang bagus untuknya - ia digunakan untuk menjeda fungsi (pada pernyataan hasil) dan melanjutkannya nanti menggunakan status di IEnumerable.
Dani
2
Bukankah ini harus dipindahkan ke StackOverflow?
Danny Varod
4
@Danny - Ini tidak cocok untuk Stack Overflow, karena pertanyaannya bukan meminta untuk memecahkan masalah tertentu tetapi bertanya tentang apa yang yielddapat digunakan untuk secara umum.
ChrisF
9
Nyata? Saya tidak dapat memikirkan satu aplikasi pun di mana saya belum menggunakannya.
Aaronaught

Jawaban:

107

Efisiensi

Kata yieldkunci tersebut secara efektif menciptakan penghitungan malas atas item koleksi yang bisa jauh lebih efisien. Misalnya, jika foreachperulangan Anda hanya mengulangi 5 item pertama dari 1 juta item, maka itu semua yieldpengembalian, dan Anda tidak membangun koleksi 1 juta item secara internal terlebih dahulu. Anda juga ingin menggunakan yielddengan IEnumerable<T>nilai pengembalian dalam skenario pemrograman Anda sendiri untuk mencapai efisiensi yang sama.

Contoh efisiensi yang diperoleh dalam skenario tertentu

Bukan metode iterator, potensi penggunaan koleksi besar yang tidak efisien,
(Koleksi perantara dibangun memiliki banyak item)

// 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. 

Versi Iterator, efisien
(Tidak ada koleksi perantara dibangun)

// 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.

Sederhanakan beberapa skenario pemrograman

Dalam kasus lain, ini membuat beberapa jenis penyortiran dan penggabungan daftar lebih mudah diprogram karena Anda hanya yieldmemasukkan kembali item dalam urutan yang diinginkan daripada menyortirnya menjadi koleksi perantara dan menukarnya di sana. Ada banyak skenario seperti itu.

Hanya satu contoh adalah penggabungan dua daftar:

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

Metode ini menghasilkan kembali satu daftar item yang berdekatan, secara efektif penggabungan tanpa koleksi perantara yang diperlukan.

Info lebih lanjut

The yieldkata kunci hanya dapat digunakan dalam konteks metode iterator (memiliki jenis kembalinya IEnumerable, IEnumerator, IEnumerable<T>atau IEnumerator<T>.) Dan ada hubungan khusus dengan foreach. Iterator adalah metode khusus. The MSDN dokumentasi hasil dan iterator dokumentasi berisi banyak informasi menarik dan penjelasan tentang konsep-konsep. Pastikan untuk berkorelasi dengan para foreachkata kunci dengan membaca tentang hal itu juga, untuk melengkapi pemahaman Anda tentang iterator.

Untuk mempelajari tentang bagaimana iterator mencapai efisiensinya, rahasianya ada pada kode IL yang dihasilkan oleh kompiler C #. IL yang dihasilkan untuk metode iterator berbeda secara drastis dari yang dihasilkan untuk metode reguler (non-iterator). Artikel ini (Apa Kata Kunci Hasil Penghasilan yang Sebenarnya?) Memberikan wawasan semacam itu.

John K.
sumber
2
Mereka sangat berguna untuk algoritma yang mengambil urutan (mungkin panjang) dan menghasilkan yang lain di mana pemetaan tidak satu-ke-satu. Contohnya adalah kliping poligon; tepi tertentu dapat menghasilkan banyak atau bahkan tidak ada tepi yang terpotong. Iterator membuat ini sangat mudah untuk diungkapkan, dan menghasilkan adalah salah satu cara terbaik untuk menulisnya.
Donal Fellows
+1 Jawaban yang jauh lebih baik seperti yang saya tulis. Sekarang saya juga belajar menghasilkan bagus untuk kinerja yang lebih baik.
Jan_V
3
Sekali waktu, saya menggunakan hasil untuk membangun paket untuk protokol jaringan biner. Tampaknya pilihan paling alami di C #.
György Andrasek
4
Tidakkah database.Customers.Count()penghitungan seluruh pelanggan melakukan pencacahan, sehingga membutuhkan kode yang lebih efisien untuk melewati setiap item?
Stephen
5
Sebut saya anal, tapi itu adalah gabungan, bukan penggabungan. (Dan LINQ sudah memiliki metode Concat.)
OldFart
4

Beberapa waktu yang lalu saya memiliki contoh praktis, mari kita asumsikan Anda memiliki situasi seperti ini:

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}", ???));
   }
}

Objek tombol tidak tahu posisinya sendiri dalam koleksi. Batasan yang sama berlaku untuk Dictionary<T>atau jenis koleksi lainnya.

Ini solusi saya menggunakan yieldkata kunci:

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

Dan itulah cara saya menggunakannya:

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

Saya tidak perlu membuat forlingkaran di koleksi saya untuk menemukan indeks. Tombol saya memiliki ID dan ini juga digunakan sebagai indeks IndexerList<T>, jadi Anda menghindari ID atau indeks yang berlebihan - itulah yang saya suka! Indeks / Id dapat berupa angka arbitrer.

Wernfried Domscheit
sumber
2

Contoh praktis dapat ditemukan di sini:

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

Ada beberapa keuntungan menggunakan hasil di atas kode standar:

  • Jika iterator digunakan untuk membuat daftar, maka Anda dapat menghasilkan pengembalian dan penelepon dapat memutuskan apakah dia ingin itu menghasilkan daftar, atau tidak.
  • Penelepon juga dapat memutuskan untuk membatalkan iterasi karena suatu alasan di luar ruang lingkup apa yang Anda lakukan dalam iterasi.
  • Kode sedikit lebih pendek.

Namun, seperti yang dikatakan Jan_V (hanya mengalahkan saya dengan beberapa detik :-) Anda dapat hidup tanpanya karena secara internal kompiler akan menghasilkan kode yang hampir identik dalam kedua kasus.

Jalayn
sumber
1

Ini sebuah contoh:

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

Kelas melakukan perhitungan tanggal berdasarkan minggu kerja. Saya dapat memberi contoh pada kelas bahwa Bob bekerja pada pukul 9:30 hingga 17:30 setiap hari setiap hari dengan istirahat satu jam untuk makan siang pada pukul 12:30. Dengan pengetahuan ini, fungsi AscendingShifts () akan menghasilkan objek shift kerja antara tanggal yang disediakan. Untuk daftar semua shift kerja Bob antara 1 Jan dan 1 Februari tahun ini, Anda akan menggunakannya seperti ini:

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

Kelas tidak benar-benar beralih pada koleksi. Namun, pergeseran antara dua tanggal dapat dianggap sebagai koleksi. The yieldOperator memungkinkan untuk iterate atas koleksi membayangkan ini tanpa menciptakan koleksi sendiri.

Semut
sumber
1

Saya memiliki lapisan data db kecil yang memiliki commandkelas di mana Anda mengatur teks perintah SQL, jenis perintah, dan mengembalikan IEnumerable dari 'parameter perintah'.

Pada dasarnya idenya adalah mengetik perintah CLR alih-alih mengisi SqlCommandproperti dan parameter secara manual sepanjang waktu.

Jadi ada fungsi yang terlihat seperti ini:

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

Kelas yang mewarisi commandkelas ini memiliki properti Agedan Name.

Kemudian Anda bisa membuat commandobjek baru mengisi propertinya dan mengirimkannya ke dbantarmuka yang benar-benar melakukan panggilan perintah.

Semua dalam semua membuatnya sangat mudah untuk bekerja dengan perintah SQL dan tetap mengetiknya.

john
sumber
1

Meskipun kasus penggabungan telah dicakup dalam jawaban yang diterima, izinkan saya menunjukkan kepada Anda metode ekstensi params hasil-gabung ™:

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

Saya menggunakan ini untuk membangun paket-paket protokol jaringan:

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

The MarkChecksummetode, misalnya, terlihat seperti ini. Dan ada yieldjuga:

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

Tapi hati-hati ketika menggunakan metode agregat seperti Sum () dalam metode enumerasi karena mereka memicu proses enumerasi terpisah.

Yegor
sumber
1

Pencarian Elastis .NET contoh repo memiliki contoh yang bagus untuk menggunakan yield returnuntuk mempartisi koleksi menjadi beberapa koleksi dengan ukuran tertentu:

https://github.com/elastic/elasticsearch-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);
        }
    }
lucu
sumber
0

Memperluas jawaban Jan_V, saya baru saja menemukan kasus dunia nyata yang terkait dengan itu:

Saya perlu menggunakan versi Kernel32 dari FindFirstFile / FindNextFile. Anda mendapatkan pegangan dari panggilan pertama dan mengumpankannya ke semua panggilan berikutnya. Bungkus ini dalam enumerator dan Anda mendapatkan sesuatu yang bisa langsung Anda gunakan dengan foreach.

Loren Pechtel
sumber