Mengapa menambahkan metode menambahkan panggilan ambigu, jika itu tidak akan terlibat dalam ambiguitas

112

Saya memiliki kelas ini

public class Overloaded
{
    public void ComplexOverloadResolution(params string[] something)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }
}

Jika saya menyebutnya seperti ini:

        var blah = new Overloaded();
        blah.ComplexOverloadResolution("Which wins?");

Ini menulis Normal Winnerke konsol.

Tapi, jika saya menambahkan metode lain:

    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }

Saya mendapatkan kesalahan berikut:

Panggilan tersebut ambigu antara metode atau properti berikut:> ' Overloaded.ComplexOverloadResolution(params string[])' dan ' Overloaded.ComplexOverloadResolution<string>(string)'

Saya dapat memahami bahwa menambahkan metode mungkin memperkenalkan ambiguitas panggilan, tetapi itu adalah ambiguitas antara dua metode yang sudah ada (params string[])dan <string>(string)! Jelas tak satu pun dari dua metode yang terlibat dalam ambiguitas adalah metode yang baru ditambahkan, karena yang pertama adalah params, dan yang kedua adalah generik.

Apakah ini bug? Bagian mana dari spesifikasi yang mengatakan bahwa seharusnya demikian?

McKay
sumber
2
Saya tidak berpikir 'Overloaded.ComplexOverloadResolution(string)'mengacu pada <string>(string)metode; Saya pikir ini mengacu pada (string, object)metode tanpa objek yang diberikan.
phoog
1
@phoog Oh, data itu dipotong oleh StackOverflow karena ini adalah tag, tetapi pesan kesalahan memiliki penunjuk template. Saya menambahkannya kembali.
McKay
Anda telah menangkap saya! Saya mengutip bagian yang relevan dari spesifikasi dalam jawaban saya, tetapi saya belum menghabiskan setengah jam terakhir untuk membaca dan memahaminya!
phoog
@phoog, melihat melalui bagian-bagian spesifikasi, saya tidak melihat apa-apa tentang memperkenalkan ambiguitas ke dalam metode selain dirinya sendiri dan metode lain, bukan dua metode lain.
McKay
Terpikir oleh saya bahwa ini adil batu-kertas-gunting : set mana pun dari dua nilai berbeda memiliki pemenang, tetapi set lengkap tiga nilai tidak.
phoog

Jawaban:

107

Apakah ini bug?

Iya.

Selamat, Anda menemukan bug dalam resolusi kelebihan beban. Bug mereproduksi di C # 4 dan 5; itu tidak mereproduksi dalam versi "Roslyn" dari penganalisis semantik. Saya telah memberi tahu tim penguji C # 5, dan mudah-mudahan kami dapat menyelidiki dan menyelesaikan masalah ini sebelum rilis final. (Seperti biasa, tidak ada janji.)

Analisis yang benar mengikuti. Kandidatnya adalah:

0: C(params string[]) in its normal form
1: C(params string[]) in its expanded form
2: C<string>(string) 
3: C(string, object) 

Kandidat nol jelas tidak dapat diterapkan karena stringtidak dapat dikonversi string[]. Tinggal tiga.

Dari ketiganya, kita harus menentukan metode unik terbaik. Kami melakukannya dengan membuat perbandingan berpasangan dari tiga kandidat yang tersisa. Ada tiga pasangan seperti itu. Semuanya memiliki daftar parameter yang identik setelah kita menghapus parameter opsional yang dihilangkan, yang berarti kita harus pergi ke putaran tiebreaking lanjutan yang dijelaskan dalam bagian 7.5.3.2 spesifikasi.

Mana yang lebih baik, 1 atau 2? Tiebreaker yang relevan adalah bahwa metode generik selalu lebih buruk daripada metode non-generik. 2 lebih buruk dari 1. Jadi 2 tidak bisa menjadi pemenang.

Mana yang lebih baik, 1 atau 3? Tiebreak yang relevan adalah: metode yang hanya dapat diterapkan dalam bentuk yang diperluas selalu lebih buruk daripada metode yang dapat diterapkan dalam bentuk normalnya. Oleh karena itu 1 lebih buruk dari 3. Jadi 1 tidak bisa menjadi pemenang.

Mana yang lebih baik, 2 atau 3? Tiebreaker yang relevan adalah bahwa metode generik selalu lebih buruk daripada metode non-generik. 2 lebih buruk dari 3. Jadi 2 tidak bisa menjadi pemenang.

Untuk dipilih dari sekumpulan beberapa kandidat yang berlaku, seorang kandidat harus (1) tidak terkalahkan, (2) mengalahkan setidaknya satu kandidat lainnya, dan (3) menjadi kandidat unik yang memiliki dua properti pertama. Kandidat tiga dikalahkan oleh kandidat lain, dan mengalahkan setidaknya satu kandidat lainnya; itu adalah satu-satunya kandidat dengan properti ini. Oleh karena itu, kandidat ketiga adalah kandidat terbaik yang unik . Ini harus menang.

Tidak hanya compiler C # 4 yang salah, seperti yang Anda catat dengan benar, compiler ini melaporkan pesan kesalahan yang aneh. Bahwa kompiler salah melakukan analisis resolusi kelebihan beban sedikit mengejutkan. Bahwa mendapatkan pesan kesalahan yang salah sama sekali tidak mengejutkan; heuristik kesalahan "metode ambigu" pada dasarnya mengambil dua metode dari kumpulan kandidat jika metode terbaik tidak dapat ditentukan. Tidaklah pandai menemukan ambiguitas yang "sebenarnya", jika sebenarnya ada.

Orang mungkin bertanya mengapa demikian. Sangat sulit untuk menemukan dua metode yang "sangat ambigu" karena hubungan "betterness" adalah intransitif . Ada kemungkinan untuk menghadapi situasi di mana kandidat 1 lebih baik dari 2, 2 lebih baik dari 3, dan 3 lebih baik dari 1. Dalam situasi seperti itu, kita tidak dapat melakukan lebih baik daripada memilih dua di antaranya sebagai "yang ambigu".

Saya ingin meningkatkan heuristik ini untuk Roslyn tetapi ini adalah prioritas rendah.

(Latihan untuk pembaca: "Rancang algoritma waktu linier untuk mengidentifikasi anggota terbaik yang unik dari himpunan n elemen di mana hubungan betterness adalah intransitif" adalah salah satu pertanyaan yang saya tanyakan pada hari saya mewawancarai tim ini. Bukan algoritma yang sangat sulit; cobalah.)

Salah satu alasan mengapa kami menunda untuk menambahkan argumen opsional ke C # untuk waktu yang lama adalah banyaknya situasi ambigu kompleks yang diperkenalkannya ke dalam algoritma resolusi berlebih; ternyata kami tidak melakukannya dengan benar.

Jika Anda ingin memasukkan masalah Hubungkan untuk melacaknya, silakan. Jika Anda hanya ingin hal itu diperhatikan kami, anggap saja sudah selesai. Saya akan menindaklanjuti dengan pengujian tahun depan.

Terima kasih sudah membawa ini padaku. Maaf atas kesalahannya.

Eric Lippert
sumber
1
Terima kasih atas tanggapannya. Anda mengatakan "1 lebih buruk dari 2", tetapi memilih metode 1 jika saya hanya memiliki metode 1 dan 2?
McKay
@ McKay: Ups, Anda benar, saya menyatakan itu secara terbalik. Saya akan memperbaiki teksnya.
Eric Lippert
1
Rasanya canggung membaca frasa "sisa tahun ini" mengingat tidak ada lagi yang tersisa setengah minggu :)
BoltClock
2
@BoltClock memang, pernyataan "berangkat untuk sisa tahun ini" menyiratkan satu hari libur.
phoog
1
Aku pikir begitu. Saya membaca "3) jadilah kandidat unik yang memiliki dua properti pertama" sebagai "jadilah satu-satunya kandidat yang (tidak terkalahkan dan mengalahkan setidaknya satu kandidat lainnya)" . Tapi komentar terbaru Anda membuat saya berpikir "(jadilah satu-satunya kandidat yang tidak terkalahkan) dan mengalahkan setidaknya satu kandidat lainnya" . Bahasa Inggris benar-benar dapat menggunakan simbol pengelompokan. Jika yang terakhir benar, maka saya mengerti lagi.
default.kramer
5

Bagian mana dari spesifikasi yang mengatakan bahwa seharusnya demikian?

Bagian 7.5.3 (resolusi kelebihan beban), bersama dengan bagian 7.4 (pencarian anggota) dan 7.5.2 (jenis inferensi).

Perhatikan terutama bagian 7.5.3.2 (anggota fungsi yang lebih baik), yang mengatakan di bagian "parameter opsional tanpa argumen yang sesuai akan dihapus dari daftar parameter", dan "Jika M (p) adalah metode non-generik dan M (q) adalah metode umum, maka M (p) lebih baik dari M (q). "

Namun, saya tidak memahami bagian-bagian spesifikasi ini dengan cukup menyeluruh untuk mengetahui bagian mana dari spesifikasi yang mengontrol perilaku ini, apalagi untuk menilai apakah itu sesuai.

phoog
sumber
Tetapi itu tidak menjelaskan mengapa menambahkan anggota akan menyebabkan ambiguitas antara dua metode yang sudah ada.
McKay
@CKay cukup adil (lihat edit). Kami hanya harus menunggu Eric Lippert memberi tahu kami apakah ini perilaku yang benar: ->
phoog
1
Itu adalah bagian yang tepat dari spesifikasi. Masalahnya adalah mereka mengatakan bahwa seharusnya tidak demikian!
Eric Lippert
3

Anda dapat menghindari ambiguitas ini dengan mengubah nama parameter pertama dalam beberapa metode dan menentukan parameter yang ingin Anda tetapkan

seperti ini :

public class Overloaded
{
    public void ComplexOverloadResolution(params string[] somethings)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }

    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Overloaded a = new Overloaded();
        a.ComplexOverloadResolution(something:"asd");
    }
}
MhdSyrwan
sumber
Oh, saya tahu kode ini buruk, dan ada beberapa cara untuk mengatasinya, pertanyaannya adalah "mengapa kompilator berperilaku seperti ini?"
McKay
1

Jika Anda menghapus paramsdari metode pertama Anda, ini tidak akan terjadi. Anda metode pertama dan ketiga memiliki kedua panggilan yang valid ComplexOverloadResolution(string), tetapi jika metode pertama Anda adalah public void ComplexOverloadResolution(string[] something)tidak akan ada ambiguitas.

Memasok nilai untuk sebuah parameter object somethingElse = nullmenjadikannya parameter opsional dan karenanya tidak harus ditentukan saat memanggil kelebihan beban itu.

Sunting: Kompiler melakukan beberapa hal gila di sini. Jika Anda memindahkan metode ketiga dalam kode setelah yang pertama, metode itu akan melaporkan dengan benar. Jadi tampaknya itu mengambil dua kelebihan pertama dan melaporkannya, tanpa memeriksa yang benar.

'ConsoleApplication1.Program.ComplexOverloadResolution (params string [])' dan 'ConsoleApplication1.Program.ComplexOverloadResolution (string, object)'

Edit2: Temuan baru. Menghapus metode apa pun dari ketiga metode di atas tidak akan menghasilkan ambiguaty antara keduanya. Jadi tampaknya konflik hanya muncul jika ada tiga metode yang ada, terlepas dari urutannya.

Tomislav Markovski
sumber
Tetapi itu tidak menjelaskan mengapa menambahkan anggota akan menyebabkan ambiguitas antara dua metode yang sudah ada.
McKay
Terjadi ambiguitas antara metode pertama dan ketiga Anda, tetapi mengapa compiler melaporkan dua metode lainnya di luar jangkauan saya.
Tomislav Markovski
Tetapi jika saya menghapus metode kedua, saya tidak memiliki ambiguitas, itu berhasil memanggil metode ketiga. Jadi sepertinya kompilator tidak memiliki ambiguitas antara metode pertama dan ketiga.
McKay
Lihat Edit saya. Kompiler gila.
Tomislav Markovski
Sebenarnya, kombinasi apa pun dari hanya dua metode tidak menghasilkan ambiguitas. Ini sangat aneh. Edit2.
Tomislav Markovski
1
  1. Jika Anda menulis

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");

    atau hanya menulis

    var blah = new Overloaded();
    blah.ComplexOverloadResolution();

    itu akan berakhir dengan metode yang sama , dalam metode

    public void ComplexOverloadResolution(params string[] something

    Hal ini menyebabkan paramskata kunci yang membuatnya paling cocok juga untuk kasus ketika tidak ada parameter yang ditentukan

  2. Jika Anda mencoba menambahkan metode baru seperti ini

    public void ComplexOverloadResolution(string something)
    {
        Console.WriteLine("Added Later");
    }

    Ini akan mengkompilasi dan memanggil metode ini dengan sempurna karena ini sangat cocok untuk panggilan Anda dengan stringparameter. Jauh lebih kuat params string[] something.

  3. Anda mendeklarasikan metode kedua seperti yang Anda lakukan

    public void ComplexOverloadResolution(string something, object something=null);

    Compiler, kebingungan total antara metode pertama dan ini, baru saja menambahkan satu. Karena tidak tahu fungsi mana yang harus dia lakukan sekarang dalam panggilan Anda

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");

    Faktanya, jika Anda menghapus parameter string dari panggilan, seperti kode berikut, semuanya dikompilasi dengan benar dan berfungsi seperti sebelumnya

    var blah = new Overloaded();
    blah.ComplexOverloadResolution(); // will be ComplexOverloadResolution(params string[] something) function called here, like a best match.
Tigran
sumber
Pertama, Anda menulis dua kasus panggilan yang sama. Jadi tentu saja itu akan menggunakan metode yang sama, atau apakah Anda bermaksud menulis sesuatu yang berbeda?
McKay
Tetapi sekali lagi, jika saya memahami sisa jawaban Anda dengan benar, Anda tidak membaca apa yang dikatakan compiler adalah kebingungan, yaitu antara metode pertama dan kedua, bukan metode ketiga yang baru saya tambahkan.
McKay
ah terima kasih. Tapi itu masih menyisakan masalah yang saya sebutkan di komentar kedua saya ke posting Anda.
McKay
Untuk lebih jelasnya, Anda menyatakan "Compiler, melompat dalam kebingungan total antara metode pertama dan ini, baru saja menambahkan satu." Tapi ternyata tidak. Ini membuat bingung dengan dua metode lainnya. Metode params, dan metode umum.
McKay
@ McKay: Whell, itu melompat dalam kebingungan karena diberikan state3 fungsi, bukan satu atau beberapa dari mereka, tepatnya. Faktanya, cukup mengomentari salah satu dari mereka, untuk menyelesaikan masalah. Yang paling cocok di antara fungsi yang tersedia adalah yang dengan params, yang kedua adalah dengan genericsparameter, ketika kita menambahkan yang ketiga itu menciptakan kebingungan di salah setsatu fungsi. Saya pikir, kemungkinan besar, itu bukan pesan kesalahan yang jelas yang dihasilkan oleh compiler.
Tigran