Pertanyaan Penggunaan yang tepat dari 'hasil imbal hasil'


Itu menghasilkan kata kunci adalah salah satunya kata kunci di C # yang terus membingungkan saya, dan saya tidak pernah yakin bahwa saya menggunakannya dengan benar.

Dari dua potongan kode berikut, mana yang lebih disukai dan mengapa?

Versi 1: Menggunakan imbal hasil

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        foreach (Product product in products)
        {
            yield return product;
        }
    }
}

Versi 2: Kembalikan daftar

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList<Product>();
    }
}

792
2018-01-03 22:43


asal


Jawaban:


Saya cenderung menggunakan imbal hasil ketika saya menghitung item berikutnya dalam daftar (atau bahkan kelompok item berikutnya).

Menggunakan Versi 2 Anda, Anda harus memiliki daftar lengkap sebelum kembali. Dengan menggunakan pengembalian hasil, Anda benar-benar hanya perlu memiliki item berikutnya sebelum kembali.

Di antara hal-hal lain, ini membantu menyebarkan biaya komputasi perhitungan yang rumit dalam kerangka waktu yang lebih besar. Sebagai contoh, jika daftar terhubung ke GUI dan pengguna tidak pernah pergi ke halaman terakhir, Anda tidak pernah menghitung item akhir dalam daftar.

Kasus lain di mana hasil-kembali lebih disukai adalah jika IEnumerable mewakili set yang tidak terbatas. Pertimbangkan daftar Bilangan Perdana, atau daftar nomor acak tak terbatas. Anda tidak dapat mengembalikan IEnumerable sepenuhnya sekaligus, jadi Anda menggunakan imbal hasil untuk mengembalikan daftar secara bertahap.

Dalam contoh khusus Anda, Anda memiliki daftar lengkap produk, jadi saya akan menggunakan Versi 2.


725
2018-01-03 23:01



Mengisi daftar sementara seperti mengunduh seluruh video, sedangkan menggunakan yield seperti streaming video itu.


535
2017-07-08 14:14



Sebagai contoh konseptual untuk memahami kapan Anda harus menggunakannya yield, katakanlah metode ConsumeLoop() memproses barang yang dikembalikan / dihasilkan oleh ProduceList():

void ConsumeLoop() {
    foreach (Consumable item in ProduceList())        // might have to wait here
        item.Consume();
}

IEnumerable<Consumable> ProduceList() {
    while (KeepProducing())
        yield return ProduceExpensiveConsumable();    // expensive
}

Tanpa yield, panggilan ke ProduceList() mungkin butuh waktu lama karena Anda harus menyelesaikan daftar sebelum kembali:

//pseudo-assembly
Produce consumable[0]                   // expensive operation, e.g. disk I/O
Produce consumable[1]                   // waiting...
Produce consumable[2]                   // waiting...
Produce consumable[3]                   // completed the consumable list
Consume consumable[0]                   // start consuming
Consume consumable[1]
Consume consumable[2]
Consume consumable[3]

Menggunakan yield, itu menjadi disusun kembali, semacam bekerja "secara paralel":

//pseudo-assembly
Produce consumable[0]
Consume consumable[0]                   // immediately Consume
Produce consumable[1]
Consume consumable[1]                   // consume next
Produce consumable[2]
Consume consumable[2]                   // consume next
Produce consumable[3]
Consume consumable[3]                   // consume next

Dan terakhir, seperti yang sudah disarankan sebelumnya, Anda harus menggunakan Versi 2 karena Anda sudah memiliki daftar yang lengkap.


59
2017-10-09 21:45



Ini akan tampak seperti saran yang aneh, tetapi saya belajar cara menggunakan yield kata kunci dalam C # dengan membaca presentasi tentang generator dengan Python: David M. Beazley's http://www.dabeaz.com/generators/Generators.pdf. Anda tidak perlu tahu banyak Python untuk memahami presentasi - saya tidak. Saya merasa sangat membantu dalam menjelaskan tidak hanya cara kerja generator tetapi juga mengapa Anda harus peduli.


26
2018-01-04 21:51



Saya tahu ini adalah pertanyaan lama, tetapi saya ingin menawarkan satu contoh bagaimana kata kunci hasil dapat digunakan secara kreatif. saya sudah sangat diuntungkan dari teknik ini. Semoga ini akan menjadi bantuan untuk orang lain yang tersandung pada pertanyaan ini.

Catatan: Jangan berpikir tentang kata kunci hasil sebagai hanya menjadi cara lain untuk membangun koleksi. Bagian terbesar dari kekuatan hasil datang pada kenyataan bahwa eksekusi adalah berhenti di dalam kamu metode atau properti hingga kode panggilan iterates di atas nilai berikutnya. Inilah contoh saya:

Menggunakan kata kunci hasil (di samping Rob Eisenburg Caliburn.Micro coroutinesimplementasi) memungkinkan saya untuk menyatakan panggilan asynchronous ke layanan web seperti ini:

public IEnumerable<IResult> HandleButtonClick() {
    yield return Show.Busy();

    var loginCall = new LoginResult(wsClient, Username, Password);
    yield return loginCall;
    this.IsLoggedIn = loginCall.Success;

    yield return Show.NotBusy();
}

Apa yang akan dilakukan adalah menghidupkan BusyIndicator saya, memanggil metode Login di layanan web saya, mengatur bendera IsLoggedIn saya ke nilai kembalian, dan kemudian menghidupkan kembali BusyIndicator.

Inilah cara kerjanya: IResult memiliki metode Execute dan acara Selesai. Caliburn.Micro meraih IEnumerator dari panggilan ke HandleButtonClick () dan meneruskannya ke dalam metode Coroutine.BeginExecute. Metode BeginExecute memulai iterasi melalui IResults. Ketika IResult pertama dikembalikan, eksekusi dijeda di dalam HandleButtonClick (), dan BeginExecute () menempelkan event handler ke acara Selesai dan panggilan Execute (). IResult.Execute () dapat melakukan tugas sinkron atau asinkron dan mengaktifkan acara Selesai saat selesai.

LoginResult terlihat seperti ini:

public LoginResult : IResult {
    // Constructor to set private members...

    public void Execute(ActionExecutionContext context) {
        wsClient.LoginCompleted += (sender, e) => {
            this.Success = e.Result;
            Completed(this, new ResultCompletionEventArgs());
        };
        wsClient.Login(username, password);
    }

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
    public bool Success { get; private set; }
}

Mungkin membantu menyiapkan sesuatu seperti ini dan melangkah melalui eksekusi untuk melihat apa yang terjadi.

Semoga ini bisa membantu seseorang! Saya sangat menikmati menjelajahi berbagai cara menghasilkan dapat digunakan.


24
2018-05-18 04:36



Kedua potongan kode tersebut benar-benar melakukan dua hal yang berbeda. Versi pertama akan menarik anggota saat Anda membutuhkannya. Versi kedua akan memuat semua hasil ke dalam memori sebelum Anda mulai melakukan apa saja dengannya.

Tidak ada jawaban yang benar atau salah untuk yang satu ini. Mana yang lebih baik tergantung pada situasinya. Misalnya, jika ada batas waktu untuk menyelesaikan kueri Anda dan Anda perlu melakukan sesuatu yang semi-rumit dengan hasil, versi kedua dapat lebih disukai. Tetapi berhati-hatilah dengan hasil yang besar, terutama jika Anda menjalankan kode ini dalam mode 32-bit. Saya telah digigit oleh pengecualian OutOfMemory beberapa kali ketika melakukan metode ini.

Hal utama yang perlu diingat adalah ini: perbedaannya dalam efisiensi. Dengan demikian, Anda mungkin harus memilih mana yang membuat kode Anda lebih sederhana dan mengubahnya hanya setelah membuat profil.


12
2018-01-03 23:30



Hasil memiliki dua kegunaan besar

Ini membantu untuk memberikan iterasi khusus tanpa membuat koleksi temp. (memuat semua data dan looping)

Ini membantu untuk melakukan iterasi stateful. ( Streaming)

Di bawah ini adalah video sederhana yang saya buat dengan demonstrasi penuh untuk mendukung dua poin di atas

http://www.youtube.com/watch?v=4fju3xcm21M


10
2018-04-05 18:48



Ini adalah apa Chris Menjual menceritakan tentang pernyataan tersebut di Bahasa Pemrograman C #;

Saya terkadang lupa bahwa hasil imbal hasil tidak sama dengan return, dalam   bahwa kode setelah pengembalian hasil dapat dieksekusi. Misalnya,   kode setelah pengembalian pertama di sini tidak akan pernah bisa dieksekusi:

    int F() {
return 1;
return 2; // Can never be executed
}

Sebaliknya, kode setelah hasil pertama kembali di sini bisa   dieksekusi:

IEnumerable<int> F() {
yield return 1;
yield return 2; // Can be executed
}

Ini sering menggigit saya dalam pernyataan if:

IEnumerable<int> F() {
if(...) { yield return 1; } // I mean this to be the only
// thing returned
yield return 2; // Oops!
}

Dalam kasus-kasus ini, mengingat bahwa hasil pengembalian tidak seperti “final”   kembali sangat membantu.


10
2018-05-27 20:18



Imbal hasil hasil bisa sangat kuat untuk algoritma di mana Anda perlu iterasi melalui jutaan objek. Pertimbangkan contoh berikut di mana Anda perlu menghitung kemungkinan perjalanan untuk rideshare. Pertama kami menghasilkan perjalanan yang mungkin:

    static IEnumerable<Trip> CreatePossibleTrips()
    {
        for (int i = 0; i < 1000000; i++)
        {
            yield return new Trip
            {
                Id = i.ToString(),
                Driver = new Driver { Id = i.ToString() }
            };
        }
    }

Kemudian ulangi melalui setiap perjalanan:

    static void Main(string[] args)
    {
        foreach (var trip in CreatePossibleTrips(trips))
        {
            // possible trip is actually calculated only at this point, because of yield
            if (IsTripGood(trip))
            {
                // match good trip
            }
        }
    }

Jika Anda menggunakan Daftar, bukannya menghasilkan, Anda perlu mengalokasikan 1 juta objek ke memori (~ 190mb) dan contoh sederhana ini akan memakan waktu ~ 1400ms untuk dijalankan. Namun, jika Anda menggunakan hasil, Anda tidak perlu menempatkan semua objek temp ini ke memori dan Anda akan mendapatkan kecepatan algoritma yang lebih cepat secara signifikan: contoh ini hanya akan memakan waktu ~ 400ms untuk dijalankan tanpa konsumsi memori sama sekali.


9
2017-11-22 16:35



Dengan asumsi produk Anda kelas LINQ menggunakan hasil yang sama untuk enumerasi / iterasi, versi pertama lebih efisien karena hanya menghasilkan satu nilai setiap kali iterasinya selesai.

Contoh kedua adalah mengonversi enumerator / iterator ke daftar dengan metode ToList (). Ini berarti iterasi secara manual atas semua item dalam enumerator dan kemudian mengembalikan daftar yang datar.


8
2018-01-03 22:58



Ini agak penting, tetapi karena pertanyaan itu ditandai praktik terbaik, saya akan meneruskan dan melemparkan dua sen saya. Untuk jenis ini saya lebih suka menjadikannya sebagai properti:

public static IEnumerable<Product> AllProducts
{
    get {
        using (AdventureWorksEntities db = new AdventureWorksEntities()) {
            var products = from product in db.Product
                           select product;

            return products;
        }
    }
}

Tentu, itu sedikit lebih banyak boiler-plate, tetapi kode yang menggunakan ini akan terlihat jauh lebih bersih:

prices = Whatever.AllProducts.Select (product => product.price);

vs

prices = Whatever.GetAllProducts().Select (product => product.price);

catatan: Saya tidak akan melakukan ini untuk metode apa pun yang mungkin membutuhkan waktu beberapa saat untuk melakukan pekerjaan mereka.


8
2018-01-03 23:30