Tidak dapat menggunakan parameter ref atau out dalam ekspresi lambda

173

Mengapa Anda tidak bisa menggunakan parameter ref atau out dalam ekspresi lambda?

Saya menemukan kesalahan hari ini dan menemukan solusi tetapi saya masih penasaran mengapa ini adalah kesalahan waktu kompilasi.

CS1628 : Tidak dapat menggunakan dalam parameter ref atau out 'parameter' di dalam metode anonim, ekspresi lambda, atau ekspresi permintaan

Berikut ini contoh sederhana:

private void Foo()
{
    int value;
    Bar(out value);
}

private void Bar(out int value)
{
    value = 3;
    int[] array = { 1, 2, 3, 4, 5 };
    int newValue = array.Where(a => a == value).First();
}
skalb
sumber
Ini tentang iterator, tetapi banyak alasan yang sama dalam posting ini (juga oleh Eric Lippert & mdash; dia ada di tim desain bahasa) berlaku untuk lambdas: < blogs.msdn.com/ericlippert/archive/2009/07/13 /… >
Joel Coehoorn
17
Bolehkah saya bertanya apa solusi yang Anda temukan?
Beatles1692
3
Anda bisa mendeklarasikan variabel normal lokal dan bekerja dengannya, dan menetapkan hasilnya ke nilai sesudahnya ... Tambahkan var tempValue = nilai; dan kemudian bekerja dengan tempValue.
Drunken Code Monkey

Jawaban:

122

Lambdas memiliki penampilan mengubah masa hidup variabel yang mereka tangkap. Misalnya ekspresi lambda berikut ini menyebabkan parameter p1 hidup lebih lama dari bingkai metode saat ini karena nilainya dapat diakses setelah bingkai metode tidak lagi di tumpukan

Func<int> Example(int p1) {
  return () => p1;
}

Properti lain dari variabel yang ditangkap adalah bahwa perubahan pada variabel juga terlihat di luar ekspresi lambda. Misalnya cetakan berikut 42

void Example2(int p1) {
  Action del = () => { p1 = 42; }
  del();
  Console.WriteLine(p1);
}

Kedua properti ini menghasilkan serangkaian efek tertentu yang terbang di hadapan parameter ref dengan cara berikut

  • parameter ref mungkin memiliki masa hidup yang tetap. Pertimbangkan untuk melewatkan variabel lokal sebagai parameter ref ke suatu fungsi.
  • Efek samping pada lambda perlu terlihat pada parameter ref itu sendiri. Baik di dalam metode dan di pemanggil.

Ini adalah properti yang agak tidak kompatibel dan merupakan salah satu alasan mereka tidak diizinkan dalam ekspresi lambda.

JaredPar
sumber
36
Saya mengerti bahwa kita tidak dapat menggunakan refekspresi lambda di dalam, tetapi keinginan untuk menggunakannya belum terpenuhi.
zionpi
85

Di bawah tenda, metode anonim diimplementasikan dengan mengangkat variabel yang ditangkap (yang merupakan inti dari badan pertanyaan Anda) dan menyimpannya sebagai bidang dari kelas yang dihasilkan kompiler. Tidak ada cara untuk menyimpan parameter refatau outsebagai bidang. Eric Lippert membahasnya dalam entri blog . Perhatikan bahwa ada perbedaan antara variabel yang ditangkap dan parameter lambda. Anda dapat memiliki "parameter formal" seperti berikut ini karena mereka bukan variabel yang ditangkap:

delegate void TestDelegate (out int x);
static void Main(string[] args)
{
    TestDelegate testDel = (out int x) => { x = 10; };
    int p;
    testDel(out p);
    Console.WriteLine(p);
}
Mehrdad Afshari
sumber
70

Anda dapat tetapi Anda harus secara eksplisit mendefinisikan semua tipe demikian

(a, b, c, ref d) => {...}

Namun, tidak valid

(int a, int b, int c, ref int d) => {...}

Adalah benar

Ben Adams
sumber
13
Itu benar; pertanyaannya adalah mengapa Anda tidak bisa; jawabannya adalah kamu bisa.
Ben Adams
24
Tidak; pertanyaannya adalah mengapa Anda tidak dapat referensi variabel yang sudah ada , sudah didefinisikan refatau out, di dalam lambda. Jelas jika Anda membaca kode contoh (coba lagi untuk membacanya lagi). Jawaban yang diterima dengan jelas menjelaskan alasannya. Jawaban Anda adalah tentang menggunakan refatau out parameter ke lambda. Sama sekali tidak menjawab pertanyaan dan berbicara tentang sesuatu yang lain
edc65
4
@ edc65 benar ... ini tidak ada hubungannya dengan subjek pertanyaan, yaitu tentang isi ekspresi lamba (di sebelah kanan), bukan daftar parameternya (di sebelah kiri). Aneh bahwa ini mendapat 26 upvotes.
Jim Balter
6
Tapi itu membantu saya. +1 untuk itu. Terima kasih
Emad
1
Tapi saya masih tidak mengerti mengapa ini dirancang untuk menjadi seperti ini. Mengapa saya harus mendefinisikan semua jenis secara eksplisit? Semantik saya tidak perlu. Apakah saya kehilangan sesuatu?
joe
5

Karena ini adalah salah satu hasil teratas untuk "C # lambda ref" di Google; Saya merasa perlu mengembangkan jawaban di atas. Sintaks delegasi anonim yang lebih tua (C # 2.0) berfungsi dan mendukung tanda tangan yang lebih kompleks (juga penutupan). Delegasi Lambda dan anonim paling tidak telah berbagi implementasi yang dirasakan dalam backend compiler (jika mereka tidak identik) - dan yang paling penting, mereka mendukung penutupan.

Apa yang saya coba lakukan ketika saya melakukan pencarian, untuk menunjukkan sintaks:

public static ScanOperation<TToken> CreateScanOperation(
    PrattTokenDefinition<TNode, TToken, TParser, TSelf> tokenDefinition)
{
    var oldScanOperation = tokenDefinition.ScanOperation; // Closures still work.
    return delegate(string text, ref int position, ref PositionInformation currentPosition)
        {
            var token = oldScanOperation(text, ref position, ref currentPosition);
            if (token == null)
                return null;
            if (tokenDefinition.LeftDenotation != null)
                token._led = tokenDefinition.LeftDenotation(token);
            if (tokenDefinition.NullDenotation != null)
                token._nud = tokenDefinition.NullDenotation(token);
            token.Identifier = tokenDefinition.Identifier;
            token.LeftBindingPower = tokenDefinition.LeftBindingPower;
            token.OnInitialize();
            return token;
        };
}

Perlu diingat bahwa Lambdas lebih aman secara prosedural dan matematis (karena promosi nilai referensi yang disebutkan sebelumnya): Anda mungkin membuka sekaleng cacing. Pikirkan baik-baik ketika menggunakan sintaks ini.

Jonathan Dickinson
sumber
3
Saya pikir Anda salah paham pertanyaannya. Pertanyaannya adalah mengapa lambda tidak dapat mengakses variabel ref / out dalam metode wadahnya, bukan mengapa lambda itu sendiri tidak dapat berisi variabel ref / out. AFAIK tidak ada alasan bagus untuk yang terakhir. Hari ini saya menulis lambda (a, b, c, ref d) => {...}dan refdigarisbawahi dengan pesan kesalahan "Parameter '4' harus dinyatakan dengan kata kunci 'ref'". Telapak tangan! PS apa itu "promosi nilai referensi"?
Qwertie
1
@ Qwertie Saya dapat ini bekerja dengan parameterisasi penuh, artinya, termasuk jenis pada a, b, c, dan d dan berfungsi. Lihat jawaban BenAdams (meskipun dia juga salah paham terhadap pertanyaan aslinya).
Ed Bayiates
@ Qwertie Saya pikir saya hanya menghapus setengah dari titik itu - saya pikir titik aslinya adalah bahwa menempatkan par ref ke dalam penutupan mungkin berisiko, tetapi saya kemudian harus menyadari bahwa ini tidak terjadi dalam contoh yang saya berikan (dan juga tidak dilakukan Saya tahu apakah itu akan dikompilasi).
Jonathan Dickinson
Ini tidak ada hubungannya dengan pertanyaan yang sebenarnya ditanyakan ... lihat jawaban yang diterima dan komentar di bawah jawaban dari Ben Adams, yang juga salah memahami pertanyaan.
Jim Balter
1

Dan mungkin ini?

private void Foo()
{
    int value;
    Bar(out value);
}

private void Bar(out int value)
{
    value = 3;
    int[] array = { 1, 2, 3, 4, 5 };
    var val = value; 
    int newValue = array.Where(a => a == val).First();
}
geli
sumber