Mengapa C # tidak mendukung pengembalian referensi?

141

Saya telah membaca bahwa .NET mendukung pengembalian referensi, tetapi C # tidak. Apakah ada alasan khusus? Mengapa saya tidak bisa melakukan sesuatu seperti:

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 
Tom Sarduy
sumber
8
um ... harap kutip?
RPM1984
5
C # memiliki nilai dan tipe referensi dan keduanya dapat dikembalikan apa yang Anda maksud.
jalankan kembali
Saya sedang berbicara tentang sesuatu sepertireturn ref x
Tom Sarduy
2
Setelah 4 tahun, Anda dapat melakukan ini dengan C# 7:)
Arghya C

Jawaban:

188

Pertanyaan ini adalah topik blog saya pada 23 Juni 2011 . Terima kasih atas pertanyaannya!

Tim C # sedang mempertimbangkan ini untuk C # 7. Lihat https://github.com/dotnet/roslyn/issues/5233 untuk detailnya.

UPDATE: Fitur ini membuatnya menjadi C # 7!


Anda benar; .NET mendukung metode yang mengembalikan referensi terkelola ke variabel. .NET juga mendukung variabel lokal yang berisi referensi terkelola ke variabel lain. (Namun perlu dicatat bahwa .NET tidak mendukung bidang atau array yang berisi referensi terkelola ke variabel lain karena terlalu menyulitkan kisah pengumpulan sampah. Juga tipe "referensi terkelola ke variabel" tidak dapat dikonversi ke objek , dan oleh karena itu tidak dapat digunakan sebagai ketik argumen ke tipe atau metode umum.)

Komentator "RPM1984" karena alasan tertentu meminta kutipan untuk fakta ini. RPM1984 Saya mendorong Anda untuk membaca spesifikasi CLI Partisi I Bagian 8.2.1.1, "Pointer terkelola dan tipe terkait" untuk informasi tentang fitur .NET ini.

Sangat mungkin untuk membuat versi C # yang mendukung kedua fitur ini. Anda kemudian dapat melakukan hal-hal seperti

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

dan kemudian menyebutnya dengan

int a = 123;
int b = 456; 
ref int c = ref Max(ref a, ref b); 
c += 100;
Console.WriteLine(b); // 556!

Saya tahu secara empiris bahwa adalah mungkin untuk membangun versi C # yang mendukung fitur-fitur ini karena saya telah melakukannya . Pemrogram tingkat lanjut, terutama orang yang melakukan porting kode C ++ yang tidak dikelola, sering meminta kami untuk lebih banyak C ++ - seperti kemampuan untuk melakukan sesuatu dengan referensi tanpa harus keluar dari palu besar yang sebenarnya menggunakan pointer dan menyematkan memori di semua tempat. Dengan menggunakan referensi terkelola, Anda mendapatkan manfaat ini tanpa membayar biaya mengacaukan kinerja pengumpulan sampah Anda.

Kami telah mempertimbangkan fitur ini, dan benar-benar menerapkannya untuk menunjukkan kepada tim internal lain untuk mendapatkan umpan balik mereka. Namun pada saat ini berdasarkan penelitian kami, kami percaya bahwa fitur tersebut tidak memiliki daya tarik yang cukup luas atau kasus penggunaan yang memaksa untuk menjadikannya fitur bahasa yang didukung nyata . Kami memiliki prioritas lain yang lebih tinggi dan waktu dan upaya yang terbatas, sehingga kami tidak akan melakukan fitur ini dalam waktu dekat.

Juga, melakukannya dengan benar akan memerlukan beberapa perubahan pada CLR. Saat ini CLR memperlakukan metode pengembalian kembali sebagai sah tetapi tidak dapat diverifikasi karena kami tidak memiliki detektor yang mendeteksi situasi ini:

ref int M1(ref int x)
{
    return ref x;
}

ref int M2()
{
    int y = 123;
    return ref M1(ref y); // Trouble!
}

int M3()
{
    ref int z = ref M2();
    return z;
}

M3 mengembalikan konten variabel lokal M2, tetapi masa pakai variabel itu telah berakhir! Dimungkinkan untuk menulis detektor yang menentukan penggunaan pengembalian-kembali yang jelas-jelas tidak melanggar keselamatan tumpukan. Apa yang akan kami lakukan adalah menulis detektor seperti itu, dan jika detektor tidak dapat membuktikan keamanan tumpukan, maka kami tidak akan mengizinkan penggunaan pengembalian ref di bagian program tersebut. Ini bukan pekerjaan dev yang sangat besar untuk melakukannya, tetapi banyak sekali beban bagi tim penguji untuk memastikan bahwa kami benar-benar mendapatkan semua kasing. Ini hanyalah hal lain yang meningkatkan biaya fitur ke titik di mana sekarang manfaatnya tidak melebihi biaya.

Jika Anda dapat menjelaskan kepada saya mengapa Anda menginginkan fitur ini, saya akan sangat menghargai itu . Semakin banyak informasi yang kami dapatkan dari pelanggan nyata tentang mengapa mereka menginginkannya, semakin besar kemungkinan itu akan membuatnya menjadi produk suatu hari nanti. Ini fitur kecil yang lucu dan saya ingin bisa memberikannya kepada pelanggan entah bagaimana jika ada minat yang cukup.

(Lihat juga pertanyaan terkait Apakah Mungkinkah Mengembalikan Referensi ke Variabel dalam C #? Dan Dapatkah saya menggunakan referensi di dalam fungsi C # seperti C ++? )

Eric Lippert
sumber
4
@EricLippert: Saya tidak punya contoh yang meyakinkan adalah sesuatu yang saya ingin tahu. Respons yang luar biasa dan meyakinkan
Tom Sarduy
1
@ Eric: Dalam pengecualian Anda, bukankah lebih cocok untuk tetap y hidup setelah kembali dari M2? Saya berharap fitur ini berfungsi seperti lambda menangkap penduduk lokal. Atau perilaku yang Anda ajukan karena bagaimana CLR menangani skenario itu?
Fede
3
@ Eric: IMHO kemampuan untuk memiliki properti mengembalikan referensi ke tipe nilai adalah kelalaian utama dalam bahasa .net. Jika Arr adalah Array dari tipe nilai (misalnya Point), orang dapat mengatakan eg Arr (3) .X = 9 dan tahu bahwa seseorang belum mengubah nilai Arr (9) .X atau bahkan SomeOtherArray (2). X; jika Arr bukan array dari beberapa jenis referensi, tidak ada jaminan seperti itu akan ada. Fakta bahwa operator pengindeksan array mengembalikan referensi sangat berguna; Saya menganggap sangat disayangkan bahwa tidak ada jenis koleksi lain yang dapat menyediakan fungsionalitas tersebut.
supercat
4
Eric, apa cara terbaik untuk memberikan umpan balik mengenai skenario (seperti yang Anda sarankan pada paragraf terakhir) untuk memaksimalkan peluang yang terlihat? MS Connect? UserVoice (dengan batas 10 post / suara sewenang-wenang)? Sesuatu yang lain
Roman Starkov
1
@ThunderGr: Itu adalah filosofi C # untuk "tidak aman" - jika Anda menulis kode yang mungkin tidak aman memori maka C # menegaskan bahwa Anda menandainya sebagai "tidak aman", sehingga Anda bertanggung jawab atas keamanan memori. C # sudah memiliki versi fitur yang tidak aman, asalkan variabel yang dimaksud adalah tipe yang tidak dikelola. Pertanyaannya adalah apakah tim C # harus membuat versi yang tidak aman yang menangani jenis yang dikelola. Jika hal itu memudahkan pengembang untuk menulis bug yang mengerikan, itu tidak akan terjadi. C # bukan C ++, bahasa yang membuatnya mudah untuk menulis bug yang mengerikan. C # aman dari desain.
Eric Lippert
21

Anda berbicara tentang metode yang mengembalikan referensi ke tipe nilai. Satu-satunya contoh bawaan dalam C # yang saya ketahui adalah array-accessor dari tipe nilai:

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

dan sekarang buat array dari struct itu:

var points = new Point[10];
points[0].X = 1;
points[0].Y = 2;

Dalam hal ini points[0], pengindeks array , mengembalikan referensi ke struct. Tidak mungkin untuk menulis pengindeks Anda sendiri (misalnya untuk koleksi khusus), yang memiliki perilaku "mengembalikan referensi" yang sama.

Saya tidak merancang bahasa C # jadi saya tidak tahu semua alasan di balik tidak mendukungnya, tapi saya pikir jawaban singkatnya adalah: kita bisa bergaul baik tanpa itu.

Rick Sladkey
sumber
8
Tom bertanya tentang metode yang mengembalikan referensi ke variabel . Variabel tidak harus dari tipe nilai, meskipun tentu saja itu yang biasanya diinginkan orang ketika mereka menginginkan metode pengembalian-kembali. Kalau tidak, analisis yang bagus; Anda benar bahwa satu-satunya tempat dalam bahasa C # di mana ekspresi kompleks menghasilkan referensi ke variabel yang kemudian dapat dimanipulasi pengguna adalah pengindeks array. (Dan tentu saja operator akses anggota "." Antara penerima dan bidang, tapi itu jelas merupakan akses ke variabel.)
Eric Lippert
1

Anda selalu dapat melakukan sesuatu seperti:

public delegate void MyByRefConsumer<T>(ref T val);

public void DoSomethingWithValueType(MyByRefConsumer<int> c)
{
        int x = 2;
        c(ref x);
        //Handle potentially changed x...
}
Eladian
sumber
1

C # 7.0 memiliki dukungan untuk referensi yang dikembalikan. Lihat jawaban saya di sini .

CodingYoshi
sumber