Mengapa metode anonim tidak dapat ditugaskan ke var?

139

Saya memiliki kode berikut:

Func<string, bool> comparer = delegate(string value) {
    return value != "0";
};

Namun, yang berikut ini tidak dikompilasi:

var comparer = delegate(string value) {
    return value != "0";
};

Mengapa kompiler tidak mengetahui bahwa itu adalah Func<string, bool>? Dibutuhkan satu parameter string, dan mengembalikan boolean. Sebaliknya, itu memberi saya kesalahan:

Tidak dapat menetapkan metode anonim ke variabel lokal yang diketik secara implisit.

Saya punya satu tebakan dan itu adalah jika versi var dikompilasi , itu akan kurang konsisten jika saya memiliki yang berikut:

var comparer = delegate(string arg1, string arg2, string arg3, string arg4, string arg5) {
    return false;
};

Hal di atas tidak masuk akal karena Func <> hanya memungkinkan hingga 4 argumen (dalam. NET 3.5, yang saya gunakan). Mungkin seseorang bisa mengklarifikasi masalahnya. Terima kasih.

Marlon
sumber
3
Catatan tentang argumen 4 argumen Anda , dalam .NET 4, Func<>menerima hingga 16 argumen.
Anthony Pegram
Terimakasih atas klarifikasinya. Saya menggunakan .NET 3.5.
Marlon
9
Mengapa kompiler berpikir itu adalah Func<string, bool>? Sepertinya Converter<string, bool>bagi saya!
Ben Voigt
3
terkadang saya merindukan VB ..Dim comparer = Function(value$) value <> "0"
Slai

Jawaban:

155

Yang lain telah menunjukkan bahwa ada banyak kemungkinan tipe delegasi yang dapat Anda maksudkan; apa yang begitu istimewa tentang Funchal itu yang layak dijadikan standar alih-alih Predicateatau Actionatau segala kemungkinan lain? Dan, untuk lambdas, mengapa jelas bahwa tujuannya adalah untuk memilih bentuk delegasi, daripada bentuk pohon ekspresi?

Tetapi kita dapat mengatakan bahwa Funcitu istimewa, dan bahwa jenis lambda atau metode anonim yang disimpulkan adalah Fungsi dari sesuatu. Kami masih memiliki semua jenis masalah. Jenis apa yang ingin Anda simpulkan untuk kasus-kasus berikut?

var x1 = (ref int y)=>123;

Tidak ada Func<T>tipe yang membutuhkan referensi apa pun.

var x2 = y=>123;

Kami tidak tahu jenis parameter formal, meskipun kami tahu pengembaliannya. (Atau apakah kita? Apakah return int? Long? Short? Byte?)

var x3 = (int y)=>null;

Kami tidak tahu tipe pengembalian, tetapi tidak bisa dibatalkan. Jenis pengembalian dapat berupa jenis referensi atau jenis nilai yang dapat dibatalkan.

var x4 = (int y)=>{ throw new Exception(); }

Sekali lagi, kita tidak tahu tipe pengembalian, dan kali ini bisa batal.

var x5 = (int y)=> q += y;

Apakah itu dimaksudkan sebagai pernyataan lambda yang batal atau sesuatu yang mengembalikan nilai yang ditugaskan ke q? Keduanya legal; mana yang harus kita pilih?

Sekarang, Anda mungkin berkata, yah, hanya saja tidak mendukung semua fitur itu. Hanya mendukung kasus "normal" di mana jenis dapat dikerjakan. Itu tidak membantu. Bagaimana hal itu membuat hidup saya lebih mudah? Jika fitur ini bekerja kadang-kadang dan kadang-kadang gagal maka saya masih harus menulis kode untuk mendeteksi semua situasi kegagalan itu dan memberikan pesan kesalahan yang berarti untuk masing-masing. Kami masih harus menentukan semua perilaku itu, mendokumentasikannya, menulis tes untuk itu, dan sebagainya. Ini adalah fitur yang sangat mahal yang menghemat pengguna mungkin setengah lusin penekanan tombol. Kami memiliki cara yang lebih baik untuk menambahkan nilai pada bahasa daripada menghabiskan banyak waktu untuk menulis kasus uji untuk fitur yang tidak berfungsi separuh waktu dan tidak memberikan manfaat apa pun dalam kasus di mana ia berfungsi.

Situasi di mana itu sebenarnya berguna adalah:

var xAnon = (int y)=>new { Y = y };

karena tidak ada tipe yang "bisa bicara" untuk hal itu. Tapi kami memiliki masalah ini sepanjang waktu, dan kami hanya menggunakan inferensi tipe metode untuk menyimpulkan tipe:

Func<A, R> WorkItOut<A, R>(Func<A, R> f) { return f; }
...
var xAnon = WorkItOut((int y)=>new { Y = y });

dan sekarang inferensi tipe metode berhasil apa tipe func.

Eric Lippert
sumber
43
Kapan Anda akan menyusun jawaban SO Anda menjadi sebuah buku? Saya akan membelinya :)
Matt Greer
13
Saya kedua proposal untuk buku SO Lippert jawaban SO. Judul yang disarankan: "Reflections From The Stack"
Adam Rackis
24
@ Eric: Jawaban yang bagus, tapi agak menyesatkan untuk menggambarkan ini sebagai sesuatu yang tidak mungkin, karena ini benar-benar berfungsi dengan baik di D. Hanya saja kalian tidak memilih untuk memberikan delegasi literal tipe mereka sendiri, dan malah membuat mereka bergantung pada konteks mereka ... jadi IMHO jawabannya harus "karena itulah cara kami membuatnya" lebih dari apa pun. :)
user541686
5
@abstractdissonance Saya perhatikan juga bahwa kompiler adalah open source. Jika Anda peduli dengan fitur ini maka Anda dapat menyumbangkan waktu dan upaya yang diperlukan untuk mewujudkannya. Saya mendorong Anda untuk mengirimkan permintaan penarikan.
Eric Lippert
7
@AbstractDissonance: Kami mengukur biaya dalam hal sumber daya terbatas: pengembang dan waktu. Tanggung jawab ini tidak diberikan oleh tuhan; itu diberlakukan oleh wakil presiden divisi pengembang. Gagasan bahwa entah bagaimana tim C # dapat mengabaikan proses anggaran adalah aneh. Saya yakinkan Anda, pengorbanan masih dan masih dilakukan oleh pertimbangan yang cermat, bijaksana dari para ahli yang memiliki keinginan komunitas C #, misi strategis untuk Microsoft, dan selera mereka yang luar biasa dalam hal desain.
Eric Lippert
29

Hanya Eric Lippert yang tahu pasti, tapi saya pikir itu karena tanda tangan dari tipe delegasi tidak secara unik menentukan tipe.

Pertimbangkan contoh Anda:

var comparer = delegate(string value) { return value != "0"; };

Berikut adalah dua kemungkinan kesimpulan untuk apa yang varseharusnya:

Predicate<string> comparer  = delegate(string value) { return value != "0"; };  // okay
Func<string, bool> comparer = delegate(string value) { return value != "0"; };  // also okay

Yang mana yang harus disimpulkan oleh kompiler? Tidak ada alasan yang baik untuk memilih satu atau yang lain. Dan meskipun secara Predicate<T>fungsional setara dengan a Func<T, bool>, mereka masih tipe yang berbeda pada tingkat sistem tipe .NET. Kompiler karena itu tidak dapat secara jelas menyelesaikan tipe delegasi, dan harus gagal inferensi tipe.

itowlson
sumber
1
Saya yakin beberapa orang di Microsoft juga tahu pasti. ;) Tapi ya, Anda menyinggung alasan utama, jenis waktu kompilasi tidak dapat ditentukan karena tidak ada. Bagian 8.5.1 dari spesifikasi bahasa secara khusus menyoroti alasan ini untuk melarang fungsi anonim agar tidak digunakan dalam deklarasi variabel yang diketik secara implisit.
Anthony Pegram
3
Ya. Dan lebih buruk lagi, untuk lambdas kita bahkan tidak tahu apakah itu akan menjadi tipe delegasi; itu mungkin pohon ekspresi.
Eric Lippert
Bagi siapa pun yang tertarik, saya menulis sedikit lebih banyak tentang ini dan bagaimana pendekatan C # dan F # kontras di mindcapehq.com/blog/index.php/2011/02/23/…
itowlson
mengapa kompiler tidak dapat membuat tipe unik baru seperti C ++ untuk fungsi lambda
Weipeng L
Bagaimana mereka berbeda "pada tingkat sistem tipe .NET"?
arao6
6

Eric Lippert memiliki posting lama tentang itu di mana katanya

Dan sebenarnya spesifikasi C # 2.0 menyebut ini. Ekspresi grup metode dan ekspresi metode anonim adalah ekspresi tanpa tipe di C # 2.0, dan ekspresi lambda bergabung dengan mereka di C # 3.0. Karena itu ilegal bagi mereka untuk tampil "telanjang" di sebelah kanan deklarasi implisit.

Brian Rasmussen
sumber
Dan ini digarisbawahi oleh bagian 8.5.1 dari spesifikasi bahasa. "Ekspresi initializer harus memiliki tipe waktu kompilasi" agar dapat digunakan untuk variabel lokal yang diketik secara implisit.
Anthony Pegram
5

Delegasi yang berbeda dianggap tipe yang berbeda. misalnya, Actionberbeda dari MethodInvoker, dan sebuah instance dari Actiontidak dapat ditugaskan ke variabel tipe MethodInvoker.

Jadi, diberikan delegasi anonim (atau lambda) seperti () => {}, apakah itu sebuah Actionatau MethodInvoker? Kompilator tidak tahu.

Demikian pula, jika saya menyatakan tipe delegasi mengambil stringargumen dan mengembalikan argumen bool, bagaimana kompiler tahu Anda benar-benar menginginkan Func<string, bool>tipe delegasi saya? Itu tidak dapat menyimpulkan tipe delegasi.

Stephen Cleary
sumber
2

Poin-poin berikut berasal dari MSDN tentang Variabel Lokal yang Diketik Secara Implisit:

  1. var hanya dapat digunakan ketika variabel lokal dideklarasikan dan diinisialisasi dalam pernyataan yang sama; variabel tidak dapat diinisialisasi ke nol, atau ke grup metode atau fungsi anonim.
  2. Kata kunci var memerintahkan kompiler untuk menyimpulkan tipe variabel dari ekspresi di sebelah kanan pernyataan inisialisasi.
  3. Penting untuk dipahami bahwa kata kunci var tidak berarti "varian" dan tidak menunjukkan bahwa variabel tersebut diketik dengan longgar, atau terikat lambat. Itu hanya berarti bahwa kompiler menentukan dan menetapkan tipe yang paling tepat.

Referensi MSDN: Variabel Lokal yang Diketik Secara Implisit

Mempertimbangkan hal-hal berikut tentang Metode Anonim:

  1. Metode anonim memungkinkan Anda untuk menghilangkan daftar parameter.

Referensi MSDN: Metode Anonim

Saya menduga bahwa karena metode anonim sebenarnya memiliki tanda tangan metode yang berbeda, kompiler tidak dapat menyimpulkan dengan tepat apa jenis yang paling tepat untuk ditugaskan.

nybbler
sumber
1

Posting saya tidak menjawab pertanyaan aktual, tetapi menjawab pertanyaan mendasar:

"Bagaimana caranya agar aku tidak mengetik seperti itu Func<string, string, int, CustomInputType, bool, ReturnType>?" [1]

Menjadi programmer malas / hacky seperti saya, saya bereksperimen dengan menggunakan Func<dynamic, object>- yang mengambil parameter input tunggal dan mengembalikan objek.

Untuk beberapa argumen, Anda dapat menggunakannya seperti:

dynamic myParams = new ExpandoObject();
myParams.arg0 = "whatever";
myParams.arg1 = 3;
Func<dynamic, object> y = (dynObj) =>
{
    return dynObj.arg0.ToUpper() + (dynObj.arg1 * 45); //screw type casting, amirite?
};
Console.WriteLine(y(myParams));

Tip: Anda dapat menggunakan Action<dynamic>jika Anda tidak perlu mengembalikan objek.

Ya saya tahu itu mungkin bertentangan dengan prinsip pemrograman Anda, tetapi ini masuk akal bagi saya dan mungkin beberapa coders Python.

Saya cukup pemula di delegasi ... hanya ingin membagikan apa yang saya pelajari.


[1] Ini mengasumsikan bahwa Anda tidak memanggil metode yang memerlukan Funcparameter yang telah ditentukan sebelumnya , dalam hal ini, Anda harus mengetikkan string yang tidak jelas: /

Ambrose Leung
sumber
0

Bagaimana dengan itu?

var item = new
    {
        toolisn = 100,
        LangId = "ENG",
        toolPath = (Func<int, string, string>) delegate(int toolisn, string LangId)
        {
              var path = "/Content/Tool_" + toolisn + "_" + LangId + "/story.html";
              return File.Exists(Server.MapPath(path)) ? "<a style=\"vertical-align:super\" href=\"" + path + "\" target=\"_blank\">execute example</a> " : "";
        }
};

string result = item.toolPath(item.toolisn, item.LangId);
mmm
sumber