Di C #, mengapa metode anonim tidak bisa berisi pernyataan hasil?

88

Saya pikir alangkah baiknya melakukan sesuatu seperti ini (dengan lambda melakukan pengembalian hasil):

public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new()
{
    IList<T> list = GetList<T>();
    var fun = expression.Compile();

    var items = () => {
        foreach (var item in list)
            if (fun.Invoke(item))
                yield return item; // This is not allowed by C#
    }

    return items.ToList();
}

Namun, saya menemukan bahwa saya tidak dapat menggunakan hasil dalam metode anonim. Saya bertanya-tanya mengapa. Dokumen hasil hanya mengatakan itu tidak diizinkan.

Karena tidak diizinkan, saya baru saja membuat Daftar dan menambahkan item ke dalamnya.

Lance Fisher
sumber
Sekarang kita dapat memiliki asynclambda anonim yang memungkinkan awaitdi dalam C # 5.0, saya tertarik untuk mengetahui mengapa mereka masih belum menerapkan iterator anonim dengan yielddi dalamnya. Kurang lebih, itu adalah generator mesin negara yang sama.
noseratio

Jawaban:

114

Eric Lippert baru-baru ini menulis serangkaian postingan blog tentang mengapa penghasilan tidak diizinkan dalam beberapa kasus.

EDIT2:

  • Bagian 7 (yang ini telah diposting kemudian dan secara khusus membahas pertanyaan ini)

Anda mungkin akan menemukan jawabannya di sana ...


EDIT1: ini dijelaskan dalam komentar di Bagian 5, dalam jawaban Eric atas komentar Abhijeet Patel:

Q:

Eric,

Dapatkah Anda juga memberikan beberapa wawasan tentang mengapa "hasil" tidak diizinkan di dalam metode anonim atau ekspresi lambda

SEBUAH :

Pertanyaan bagus. Saya ingin memiliki blok iterator anonim. Akan sangat luar biasa untuk dapat membuat sendiri generator urutan kecil di tempat yang menutup variabel lokal. Alasan mengapa tidak mudah: manfaatnya tidak melebihi biayanya. Kehebatan membuat generator sekuens di tempat sebenarnya cukup kecil dalam skema besar hal-hal dan metode nominal melakukan pekerjaan dengan cukup baik di sebagian besar skenario. Jadi manfaatnya tidak terlalu menarik.

Biayanya besar. Penulisan ulang Iterator adalah transformasi paling rumit dalam kompiler, dan penulisan ulang metode anonim adalah yang paling rumit kedua. Metode anonim bisa berada di dalam metode anonim lain, dan metode anonim bisa berada di dalam blok iterator. Oleh karena itu, yang kita lakukan adalah pertama-tama kita menulis ulang semua metode anonim sehingga menjadi metode kelas closure. Ini adalah hal terakhir kedua yang dilakukan kompilator sebelum memancarkan IL untuk sebuah metode. Setelah langkah tersebut selesai, penulis ulang iterator dapat berasumsi bahwa tidak ada metode anonim di blok iterator; semuanya sudah ditulis ulang. Oleh karena itu, penulis ulang iterator dapat berkonsentrasi pada penulisan ulang iterator, tanpa khawatir bahwa mungkin ada metode anonim yang belum terealisasi di sana.

Selain itu, blok iterator tidak pernah "bersarang", tidak seperti metode anonim. Penulis ulang iterator dapat mengasumsikan bahwa semua blok iterator adalah "tingkat atas".

Jika metode anonim diizinkan untuk memuat blok iterator, maka kedua asumsi tersebut akan keluar jendela. Anda dapat memiliki blok iterator yang berisi metode anonim yang berisi metode anonim yang berisi blok iterator yang berisi metode anonim, dan ... yuck. Sekarang kita harus menulis sandi penulisan ulang yang dapat menangani blok iterator bersarang dan metode anonim bersarang pada saat yang sama, menggabungkan dua algoritme paling rumit menjadi satu algoritme yang jauh lebih rumit. Akan sangat sulit untuk merancang, menerapkan, dan menguji. Kami cukup pintar untuk melakukannya, saya yakin. Kami memiliki tim yang cerdas di sini. Tetapi kami tidak ingin mengambil beban yang besar itu untuk fitur "menyenangkan untuk dimiliki tetapi tidak perlu". - Eric

Thomas Levesque
sumber
2
Menarik, terutama karena ada fungsi lokal sekarang.
Mafii
4
Saya ingin tahu apakah jawaban ini kedaluwarsa karena akan mengambil pengembalian hasil dalam fungsi lokal.
Joshua
2
@Joshua tetapi fungsi lokal tidak sama dengan metode anonim ... pengembalian hasil masih tidak diperbolehkan dalam metode anonim.
Thomas Levesque
21

Eric Lippert telah menulis serangkaian artikel yang sangat bagus tentang batasan (dan keputusan desain yang memengaruhi pilihan tersebut) di blok iterator

Secara khusus, blok iterator diimplementasikan oleh beberapa transformasi kode kompilator yang canggih. Transformasi ini akan berdampak dengan transformasi yang terjadi di dalam fungsi anonim atau lambda sedemikian rupa sehingga dalam keadaan tertentu mereka berdua akan mencoba untuk 'mengubah' kode menjadi beberapa konstruksi lain yang tidak kompatibel dengan yang lain.

Akibatnya mereka dilarang berinteraksi.

Bagaimana blok iterator bekerja di bawah kap ditangani dengan baik di sini .

Sebagai contoh sederhana dari ketidakcocokan:

public IList<T> GreaterThan<T>(T t)
{
    IList<T> list = GetList<T>();
    var items = () => {
        foreach (var item in list)
            if (fun.Invoke(item))
                yield return item; // This is not allowed by C#
    }

    return items.ToList();
}

Kompiler secara bersamaan ingin mengubah ini menjadi seperti:

// inner class
private class Magic
{
    private T t;
    private IList<T> list;
    private Magic(List<T> list, T t) { this.list = list; this.t = t;}

    public IEnumerable<T> DoIt()
    {
        var items = () => {
            foreach (var item in list)
                if (fun.Invoke(item))
                    yield return item;
        }
    }
}

public IList<T> GreaterThan<T>(T t)
{
    var magic = new Magic(GetList<T>(), t)
    var items = magic.DoIt();
    return items.ToList();
}

dan pada saat yang sama aspek iterator mencoba melakukan pekerjaannya untuk membuat mesin status kecil. Contoh sederhana tertentu mungkin bekerja dengan cukup banyak pemeriksaan kewarasan (pertama berurusan dengan penutupan bersarang (mungkin sewenang-wenang) kemudian melihat apakah kelas yang dihasilkan tingkat paling bawah dapat diubah menjadi mesin negara iterator.

Bagaimanapun ini akan terjadi

  1. Cukup banyak pekerjaan.
  2. Tidak mungkin bekerja di semua kasus tanpa setidaknya aspek blok iterator mampu mencegah aspek closure menerapkan transformasi tertentu untuk efisiensi (seperti mempromosikan variabel lokal ke variabel instan daripada kelas closure yang lengkap).
    • Jika ada sedikit kemungkinan tumpang tindih di mana tidak mungkin atau cukup sulit untuk tidak diterapkan, maka jumlah masalah dukungan yang dihasilkan kemungkinan besar akan tinggi karena perubahan pemutusan yang halus akan hilang pada banyak pengguna.
  3. Ini bisa sangat mudah dikerjakan.

Dalam contoh Anda seperti ini:

public IList<T> Find<T>(Expression<Func<T, bool>> expression) 
    where T : class, new()
{
    return FindInner(expression).ToList();
}

private IEnumerable<T> FindInner<T>(Expression<Func<T, bool>> expression) 
    where T : class, new()
{
    IList<T> list = GetList<T>();
    var fun = expression.Compile();
    foreach (var item in list)
        if (fun.Invoke(item))
            yield return item;
}
ShuggyCoUk
sumber
2
Tidak ada alasan yang jelas mengapa kompilator tidak dapat, setelah ia mencabut semua closure, melakukan transformasi iterator biasa. Apakah Anda mengetahui kasus yang sebenarnya akan menimbulkan kesulitan? Btw, Magicseharusnya kelasmu Magic<T>.
Qwertie
4

Sayangnya saya tidak tahu mengapa mereka tidak mengizinkan ini, karena tentu saja sangat mungkin untuk membayangkan bagaimana ini akan bekerja.

Namun, metode anonim sudah menjadi bagian dari "sihir kompilator" dalam arti bahwa metode tersebut akan diekstraksi baik ke metode di kelas yang ada, atau bahkan ke kelas yang sama sekali baru, tergantung pada apakah ia berurusan dengan variabel lokal atau tidak.

Selain itu, penggunaan metode iterator yieldjuga diimplementasikan menggunakan sihir kompilator.

Dugaan saya adalah bahwa salah satu dari keduanya membuat kode tidak dapat diidentifikasi ke bagian sihir lainnya, dan diputuskan untuk tidak menghabiskan waktu untuk membuat ini berfungsi untuk versi kompiler C # saat ini. Tentu saja, ini mungkin bukan pilihan yang sadar sama sekali, dan itu tidak berhasil karena tidak ada yang berpikir untuk menerapkannya.

Untuk pertanyaan akurat 100%, saya sarankan Anda menggunakan situs Microsoft Connect dan melaporkan pertanyaan, saya yakin Anda akan mendapatkan sesuatu yang dapat digunakan sebagai gantinya.

Lasse V. Karlsen
sumber
1

Saya akan melakukan ini:

IList<T> list = GetList<T>();
var fun = expression.Compile();

return list.Where(item => fun.Invoke(item)).ToList();

Tentu saja Anda memerlukan System.Core.dll yang dirujuk dari .NET 3.5 untuk metode Linq. Dan termasuk:

using System.Linq;

Bersulang,

Licik


sumber
0

Mungkin itu hanya batasan sintaks. Dalam Visual Basic .NET, yang sangat mirip dengan C #, sangat mungkin ketika menulis dengan canggung

Sub Main()
    Console.Write("x: ")
    Dim x = CInt(Console.ReadLine())
    For Each elem In Iterator Function()
                         Dim i = x
                         Do
                             Yield i
                             i += 1
                             x -= 1
                         Loop Until i = x + 20
                     End Function()
        Console.WriteLine($"{elem} to {x}")
    Next
    Console.ReadKey()
End Sub

Perhatikan juga tanda kurung ' here; fungsi lambda Iterator Function... End Function mengembalikan sebuah IEnumerable(Of Integer)tapi tidak seperti objek itu sendiri. Itu harus dipanggil untuk mendapatkan benda itu.

Kode yang diubah oleh [1] menimbulkan kesalahan di C # 7.3 (CS0149):

static void Main()
{
    Console.Write("x: ");
    var x = System.Convert.ToInt32(Console.ReadLine());
    // ERROR: CS0149 - Method name expected 
    foreach (var elem in () =>
    {
        var i = x;
        do
        {
            yield return i;
            i += 1;
            x -= 1;
        }
        while (!i == x + 20);
    }())
        Console.WriteLine($"{elem} to {x}");
    Console.ReadKey();
}

Saya sangat tidak setuju dengan alasan yang diberikan dalam jawaban lain bahwa sulit untuk ditangani oleh compiler. Yang Iterator Function()Anda lihat di contoh VB.NET secara khusus dibuat untuk iterator lambda.

Di VB, ada Iteratorkata kunci; itu tidak memiliki mitra C #. IMHO, tidak ada alasan sebenarnya ini bukan fitur C #.

Jadi jika Anda benar-benar menginginkan fungsi iterator anonim, saat ini gunakan Visual Basic atau (saya belum memeriksanya) F #, seperti yang dinyatakan dalam komentar Bagian # 7 dalam jawaban @Thomas Levesque (lakukan Ctrl + F untuk F #).

Bolpat
sumber