Saya memiliki IEnumerable<T>
metode yang saya gunakan untuk menemukan kontrol di halaman WebForms.
Metode ini rekursif dan saya mengalami beberapa masalah mengembalikan tipe yang saya inginkan ketika yield return
kembali nilai nilai panggilan rekursif.
Kode saya terlihat sebagai berikut:
public static IEnumerable<Control>
GetDeepControlsByType<T>(this Control control)
{
foreach(Control c in control.Controls)
{
if (c is T)
{
yield return c;
}
if(c.Controls.Count > 0)
{
yield return c.GetDeepControlsByType<T>();
}
}
}
Ini saat ini melontarkan kesalahan "Tidak dapat mengonversi tipe ekspresi". Namun jika metode ini mengembalikan tipe IEnumerable<Object>
, kode dibuat, tetapi tipe yang salah dikembalikan dalam output.
Apakah ada cara menggunakan yield return
sementara juga menggunakan rekursi?
c#
generics
ienumerable
yield
Jamie Dixon
sumber
sumber
if(c.Controls.Count > 0)
->if(c.Controls.Any())
, terutama jika Anda menghasilkan juga :)yield
. Silakan lihat di bawah ini :) Dan itu juga satu-liner :)yield return
fungsi rekursif, skala penggunaan memori eksplosif. Lihat stackoverflow.com/a/30300257/284795Jawaban:
Di dalam metode yang mengembalikan
IEnumerable<T>
,yield return
harus kembaliT
, bukanIEnumerable<T>
.Menggantikan
dengan:
sumber
Anda harus menghasilkan setiap item yang dihasilkan oleh panggilan rekursif:
Perhatikan bahwa ada biaya untuk berulang dengan cara ini - Anda akhirnya akan menciptakan banyak iterator, yang dapat membuat masalah kinerja jika Anda memiliki pohon kontrol yang sangat dalam. Jika Anda ingin menghindari itu, pada dasarnya Anda perlu melakukan rekursi sendiri dalam metode ini, untuk memastikan hanya ada satu iterator (mesin negara) yang dibuat. Lihat pertanyaan ini untuk detail lebih lanjut dan contoh implementasi - tetapi ini jelas menambahkan sejumlah kompleksitas juga.
sumber
c.Controls.Count > 0
vs..Any()
:)Seperti yang dicatat oleh Jon Skeet dan Kolonel Panic dalam jawaban mereka, menggunakan
yield return
metode rekursif dapat menyebabkan masalah kinerja jika pohonnya sangat dalam.Berikut adalah metode ekstensi non-rekursif generik yang melakukan traversal kedalaman-pertama dari urutan pohon:
Tidak seperti solusi Eric Lippert , RecursiveSelect bekerja langsung dengan enumerator sehingga tidak perlu memanggil Reverse (yang mendukung seluruh urutan dalam memori).
Menggunakan RecursiveSelect, metode asli OP dapat ditulis ulang seperti ini:
sumber
Yang lain memberi Anda jawaban yang benar, tetapi saya pikir kasus Anda tidak akan diuntungkan.
Berikut cuplikan yang mencapai hal yang sama tanpa menghasilkan.
sumber
yield
juga? ;)foreach
loop tambahan . Sekarang saya bisa melakukan ini dengan pemrograman fungsional murni!Anda harus mengembalikan barang dari pencacah, bukan pencacah itu sendiri, pada detik Anda
yield return
sumber
Saya pikir Anda harus menghasilkan pengembalian setiap kontrol di enumerables.
sumber
Sintaksis Seredynski benar, tetapi Anda harus berhati-hati untuk menghindari
yield return
fungsi rekursif karena itu adalah bencana untuk penggunaan memori. Lihat https://stackoverflow.com/a/3970171/284795 skala itu eksplosif dengan kedalaman (fungsi serupa menggunakan 10% memori di aplikasi saya).Solusi sederhana adalah menggunakan satu daftar dan memberikannya dengan rekursi https://codereview.stackexchange.com/a/5651/754
Atau Anda dapat menggunakan tumpukan dan loop sementara untuk menghilangkan panggilan rekursif https://codereview.stackexchange.com/a/5661/754
sumber
Meskipun ada banyak jawaban bagus di luar sana, saya masih akan menambahkan bahwa adalah mungkin untuk menggunakan metode LINQ untuk mencapai hal yang sama,.
Misalnya, kode asli OP dapat ditulis ulang sebagai:
sumber
OfType
sebenarnya bukan perbedaan yang berarti. Paling banter perubahan styalistic minor. Kontrol tidak bisa menjadi anak dari banyak kontrol, jadi pohon yang dilintasi sudah unqiue. MenggunakanUnion
alih-alihConcat
tidak perlu memverifikasi keunikan urutan yang sudah dijamin unik, dan karenanya merupakan penurunan peringkat obyektif.