Mengapa kondisi (null ||! TryParse) ini menghasilkan "penggunaan variabel lokal yang tidak ditetapkan"?

98

Kode berikut menghasilkan penggunaan variabel lokal "numberOfGroups" yang tidak ditetapkan :

int numberOfGroups;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

Namun, kode ini berfungsi dengan baik (meskipun, ReSharper mengatakan = 10itu berlebihan):

int numberOfGroups = 10;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

Apakah saya melewatkan sesuatu, atau kompiler tidak menyukai saya ||?

Saya telah mempersempit ini menjadi dynamicpenyebab masalah ( optionsadalah variabel dinamis dalam kode saya di atas). Pertanyaannya masih ada, mengapa saya tidak bisa melakukan ini ?

Kode ini tidak dapat dikompilasi:

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        dynamic myString = args[0];

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

Namun, kode ini tidak :

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        var myString = args[0]; // var would be string

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

Saya tidak menyadari dynamicakan menjadi faktor dalam hal ini.

Brandon Martinez
sumber
Jangan berpikir itu cukup pintar untuk mengetahui bahwa Anda tidak menggunakan nilai yang diteruskan ke outparameter Anda sebagai masukan
Charleh
3
Kode yang diberikan di sini tidak menunjukkan perilaku yang dijelaskan; itu bekerja dengan baik. Harap kirimkan kode yang benar - benar menunjukkan perilaku yang Anda gambarkan yang dapat kami kompilasi sendiri. Beri kami seluruh file.
Eric Lippert
8
Ah, sekarang kita punya sesuatu yang menarik!
Eric Lippert
1
Tidaklah terlalu mengherankan jika kompiler bingung dengan ini. Kode helper untuk situs panggilan dinamis mungkin memiliki beberapa aliran kontrol yang tidak menjamin penetapan ke outparam. Sangat menarik untuk mempertimbangkan kode pembantu apa yang harus dihasilkan kompilator untuk menghindari masalah, atau bahkan jika itu memungkinkan.
CodesInChaos
1
Sekilas ini pasti terlihat seperti bug.
Eric Lippert

Jawaban:

73

Saya cukup yakin ini adalah bug kompiler. Temuan bagus!

Sunting: ini bukan bug, seperti yang ditunjukkan Quartermeister; dynamic mungkin menerapkan trueoperator aneh yang mungkin menyebabkan ytidak pernah diinisialisasi.

Ini repro minimal:

class Program
{
    static bool M(out int x) 
    { 
        x = 123; 
        return true; 
    }
    static int N(dynamic d)
    {
        int y;
        if(d || M(out y))
            y = 10;
        return y; 
    }
}

Saya tidak melihat alasan mengapa itu ilegal; jika Anda mengganti dynamic dengan bool, itu akan dikompilasi dengan baik.

Saya sebenarnya akan bertemu dengan tim C # besok; Saya akan menyebutkannya kepada mereka. Maaf atas kesalahannya!

Eric Lippert
sumber
6
Saya hanya senang mengetahui bahwa saya tidak akan gila :) Sejak itu saya memperbarui kode saya untuk hanya mengandalkan TryParse, jadi saya siap untuk saat ini. Terima kasih atas wawasan Anda!
Brandon Martinez
4
@NominSim: Misalkan analisis runtime gagal: maka pengecualian dilemparkan sebelum lokal dibaca. Misalkan analisis runtime berhasil: maka pada saat runtime salah satu d benar dan y ditetapkan, atau d salah dan M menetapkan y. Bagaimanapun, y sudah diatur. Fakta bahwa analisis ditunda hingga runtime tidak mengubah apa pun.
Eric Lippert
2
Jika ada yang penasaran: Saya baru saja memeriksa, dan kompiler Mono melakukannya dengan benar. imgur.com/g47oquT
Dan Tao
17
Saya pikir perilaku kompilator sebenarnya benar, karena nilai dmungkin dari tipe dengan trueoperator yang kelebihan beban . Saya telah memposting jawaban dengan contoh di mana tidak ada cabang yang diambil.
Quartermeister
2
@Quartermeister dalam hal ini kompiler Mono salah :)
porges
52

Mungkin saja variabel tidak ditetapkan jika nilai ekspresi dinamis adalah tipe dengan operator yang kelebihan bebantrue .

The ||Operator akan memanggil trueoperator untuk memutuskan apakah untuk mengevaluasi sisi kanan, dan kemudian ifpernyataan akan memanggil trueoperator untuk memutuskan apakah untuk mengevaluasi tubuhnya. Untuk normal bool, ini akan selalu mengembalikan hasil yang sama sehingga tepat satu akan dievaluasi, tetapi untuk operator yang ditentukan pengguna tidak ada jaminan seperti itu!

Membangun dari repro Eric Lippert, berikut adalah program singkat dan lengkap yang menunjukkan kasus di mana tidak ada jalur yang akan dieksekusi dan variabel akan memiliki nilai awalnya:

using System;

class Program
{
    static bool M(out int x)
    {
        x = 123;
        return true;
    }

    static int N(dynamic d)
    {
        int y = 3;
        if (d || M(out y))
            y = 10;
        return y;
    }

    static void Main(string[] args)
    {
        var result = N(new EvilBool());
        // Prints 3!
        Console.WriteLine(result);
    }
}

class EvilBool
{
    private bool value;

    public static bool operator true(EvilBool b)
    {
        // Return true the first time this is called
        // and false the second time
        b.value = !b.value;
        return b.value;
    }

    public static bool operator false(EvilBool b)
    {
        throw new NotImplementedException();
    }
}
Quartermeister
sumber
8
Kerja bagus disini. Saya telah meneruskan ini ke tes C # dan tim desain; Saya akan melihat apakah mereka memiliki komentar tentang itu ketika saya melihat mereka besok.
Eric Lippert
3
Ini sangat aneh bagiku. Mengapa harus ddievaluasi dua kali? (Saya tidak membantah bahwa itu jelas adalah , karena Anda telah menunjukkan.) Saya akan diharapkan hasil dievaluasi dari true(dari doa operator pertama, menyebabkan oleh ||) untuk "diteruskan" ke ifpernyataan. Itu pasti yang akan terjadi jika Anda menempatkan pemanggilan fungsi di sana, misalnya.
Dan Tao
3
@DanTao: Ekspresi ddievaluasi hanya sekali, seperti yang Anda harapkan. Ini adalah trueoperator yang sedang dipanggil dua kali, sekali oleh ||dan sekali oleh if.
Quartermeister
2
@DanTao: Mungkin akan lebih jelas jika kita menempatkannya pada pernyataan terpisah sebagai var cond = d || M(out y); if (cond) { ... }. Pertama kita evaluasi duntuk mendapatkan EvilBoolreferensi objek. Untuk mengevaluasi ||, pertama-tama kita memanggil EvilBool.truedengan referensi itu. Itu mengembalikan nilai true, jadi kami mengalami hubungan pendek dan tidak memanggil M, dan kemudian menetapkan referensi ke cond. Kemudian, kami melanjutkan ke ifpernyataan. The ifpernyataan mengevaluasi kondisinya dengan menelepon EvilBool.true.
Quartermeister
2
Sekarang ini sangat keren. Saya tidak tahu ada operator benar atau salah.
IllidanS4 ingin Monica kembali
7

Dari MSDN (penekanan saya):

Jenis dinamis memungkinkan operasi yang terjadi untuk melewati pemeriksaan jenis waktu kompilasi . Sebaliknya, operasi ini diselesaikan pada waktu proses . Tipe dinamis menyederhanakan akses ke COM API seperti Office Automation API, dan juga ke API dinamis seperti pustaka IronPython, dan HTML Document Object Model (DOM).

Tipe dinamis berperilaku seperti objek tipe dalam banyak situasi. Namun, operasi yang berisi ekspresi tipe dinamis tidak diselesaikan atau tipe diperiksa oleh kompilator.

Karena kompilator tidak memeriksa atau menyelesaikan operasi apa pun yang berisi ekspresi tipe dinamis, ia tidak dapat memastikan bahwa variabel akan ditetapkan melalui penggunaan TryParse().

NominSim
sumber
Jika kondisi pertama terpenuhi, numberGroupsditetapkan (di if trueblok), jika tidak, kondisi kedua menjamin penugasan (via out).
leppie
1
Itu adalah pemikiran yang menarik, tetapi kode dikompilasi dengan baik tanpa myString == null(hanya mengandalkan TryParse).
Brandon Martinez
1
@leppie Intinya adalah karena kondisi pertama (karena itu seluruh ifekspresi) melibatkan sebuah dynamicvariabel, itu tidak diselesaikan pada waktu kompilasi (oleh karena itu kompilator tidak dapat membuat asumsi tersebut).
NominSim
@NominSim: Saya mengerti maksud Anda :) +1 Bisa jadi pengorbanan dari kompiler (melanggar aturan C #), tetapi saran lain sepertinya menyiratkan bug. Cuplikan Eric menunjukkan ini bukan pengorbanan, tapi bug.
leppie
@Nominic Ini tidak mungkin benar; hanya karena fungsi kompiler tertentu ditangguhkan tidak berarti semuanya. Ada banyak bukti yang menunjukkan bahwa dalam keadaan yang sedikit berbeda, compiler melakukan analisis penugasan tertentu tanpa masalah, meskipun terdapat ekspresi dinamis.
dlev