Mengapa compiler C # tidak membuat kesalahan kode di mana metode statis memanggil metode instance?

110

Kode berikut memiliki metode statis, Foo(), memanggil metode contoh, Bar():

public sealed class Example
{
    int count;

    public static void Foo( dynamic x )
    {
        Bar(x);
    }

    void Bar( dynamic x )
    {
        count++;
    }
}

Ini mengkompilasi tanpa kesalahan * tetapi menghasilkan pengecualian pengikat runtime pada saat runtime. Menghapus parameter dinamis ke metode ini menyebabkan kesalahan kompiler, seperti yang diharapkan.

Jadi mengapa memiliki parameter dinamis memungkinkan kode untuk dikompilasi? ReSharper juga tidak menampilkannya sebagai kesalahan.

Edit 1: * dalam Visual Studio 2008

Edit 2: ditambahkan sealedkarena ada kemungkinan subclass dapat berisi Bar(...)metode statis . Bahkan versi tersegel dapat dikompilasi ketika tidak mungkin metode apa pun selain metode instance dapat dipanggil saat runtime.

Mike Scott
sumber
8
1 untuk pertanyaan yang sangat bagus
cuongle
40
Ini adalah pertanyaan Eric-Lippert.
Olivier Jacot-Descombes
3
Saya cukup yakin Jon Skeet akan tahu apa yang harus dilakukan dengan ini juga;) @ OlivierJacot-Descombes
Seribu
2
@Olivier, Jon Skeet mungkin ingin kode dikompilasi, jadi kompiler mengizinkannya :-))
Mike Scott
5
Ini adalah contoh lain mengapa Anda tidak boleh menggunakan dynamickecuali Anda benar-benar perlu.
Pelayanan

Jawaban:

71

PEMBARUAN: Di bawah jawaban ditulis pada tahun 2012, sebelum pengenalan C # 7.3 (Mei 2018) . Dalam Yang baru di C # 7.3 , bagian Kandidat kelebihan muatan yang ditingkatkan , item 1, dijelaskan bagaimana aturan resolusi kelebihan muatan telah berubah sehingga muatan berlebih non-statis dibuang lebih awal. Jadi jawaban di bawah ini (dan seluruh pertanyaan ini) sebagian besar hanya memiliki kepentingan sejarah sekarang!


(Sebelum C # 7.3 :)

Untuk beberapa alasan, resolusi kelebihan beban selalu menemukan kecocokan terbaik sebelum memeriksa statis versus non-statis. Silakan coba kode ini dengan semua jenis statis:

class SillyStuff
{
  static void SameName(object o) { }
  void SameName(string s) { }

  public static void Test()
  {
    SameName("Hi mom");
  }
}

Ini tidak akan dikompilasi karena kelebihan beban terbaik adalah yang mengambil file string. Tapi hei, itu adalah metode instance, jadi compiler mengeluh (alih-alih mengambil overload terbaik kedua).

Tambahan: Jadi menurut saya penjelasan dari dynamiccontoh Pertanyaan Asli adalah, agar konsisten, ketika tipe dinamis kita juga pertama-tama menemukan kelebihan beban terbaik (hanya memeriksa nomor parameter dan jenis parameter dll., Bukan statis vs. non -static), dan hanya setelah itu periksa statis. Namun itu berarti pemeriksaan statis harus menunggu hingga waktu proses. Oleh karena itu perilaku yang diamati.

Tambahan yang terlambat: Beberapa latar belakang mengapa mereka memilih untuk melakukan sesuatu agar lucu ini dapat disimpulkan dari posting blog ini oleh Eric Lippert .

Jeppe Stig Nielsen
sumber
Tidak ada kelebihan beban pada pertanyaan awal. Jawaban yang menunjukkan kelebihan muatan statis tidak relevan. Tidak valid untuk menjawab "baiklah jika Anda menulis ini ..." karena saya tidak menulis bahwa :-)
Mike Scott
5
@MikeScott Saya hanya mencoba meyakinkan Anda bahwa resolusi overload di C # selalu berjalan seperti ini: (1) Temukan kecocokan terbaik dengan mengabaikan statis / non-statis. (2) Sekarang kita tahu overload apa yang digunakan, lalu periksa statis. Karena itu, ketika dynamicdiperkenalkan dalam bahasa tersebut, saya pikir perancang C # berkata: "Kami tidak akan mempertimbangkan (2) waktu kompilasi ketika itu adalah dynamicekspresi." Jadi tujuan saya di sini adalah untuk mendapatkan ide tentang mengapa mereka memilih untuk tidak memeriksa contoh statis versus sampai runtime. Saya akan mengatakan, pemeriksaan ini terjadi pada waktu yang mengikat .
Jeppe Stig Nielsen
cukup adil tetapi masih tidak menjelaskan mengapa dalam kasus ini compiler tidak dapat menyelesaikan panggilan ke metode instance. Dengan kata lain, cara kompilator melakukan penyelesaiannya sederhana - ia tidak mengenali kasus sederhana seperti contoh saya di mana tidak ada kemungkinan bahwa ia tidak dapat menyelesaikan panggilan. Ironisnya adalah: dengan memiliki metode Bar () tunggal dengan parameter dinamis, compiler kemudian mengabaikan metode Bar () tunggal tersebut.
Mike Scott
45
Saya menulis bagian ini dari kompilator C #, dan Jeppe benar. Silakan pilih ini. Resolusi kelebihan beban terjadi sebelum memeriksa apakah metode yang diberikan adalah metode statis atau contoh, dan dalam hal ini kami menangguhkan resolusi kelebihan beban ke runtime dan oleh karena itu juga memeriksa statis / contoh hingga runtime juga. Selain itu, kompilator melakukan "upaya terbaik" untuk secara statis menemukan kesalahan dinamis yang sama sekali tidak komprehensif.
Chris Burrows
30

Foo memiliki parameter "x" yang dinamis, yang artinya Bar (x) adalah ekspresi dinamis.

Sangat mungkin bagi Contoh untuk memiliki metode seperti:

static Bar(SomeType obj)

Dalam hal ini metode yang benar akan diselesaikan, jadi pernyataan Bar (x) benar-benar valid. Fakta bahwa ada metode instance Bar (x) tidak pernah terjadi dan bahkan tidak dianggap: menurut definisi , karena Bar (x) adalah ekspresi dinamis, kami telah menangguhkan resolusi ke runtime.

Marc Gravell
sumber
14
tetapi ketika Anda mengambil metode Instance Bar, metode itu tidak lagi dikompilasi.
Justin Harvey
1
@Justin menarik - peringatan? Atau ada kesalahan? Bagaimanapun, ini mungkin hanya memvalidasi sejauh metode-grup, membiarkan resolusi kelebihan beban penuh ke runtime.
Marc Gravell
1
@Marc, karena tidak ada metode Bar () lain, Anda tidak menjawab pertanyaan tersebut. Bisakah Anda menjelaskan hal ini mengingat hanya ada satu metode Bar () tanpa kelebihan beban? Mengapa menunda waktu proses jika tidak ada cara lain yang dapat dipanggil? Atau ada? Catatan: Saya telah mengedit kode untuk menutup kelas, yang masih dikompilasi.
Mike Scott
1
@ Mike sebagai mengapa defer ke runtime: karena itulah yang dinamis berarti
Marc Gravell
2
@Mike mustahil bukan itu intinya; yang penting adalah apakah itu diperlukan . Inti dari dinamika adalah bahwa ini bukanlah tugas kompilator.
Marc Gravell
9

Ekspresi "dinamis" akan terikat selama runtime, jadi jika Anda mendefinisikan metode statis dengan tanda tangan yang benar atau metode instance, kompilator tidak akan memeriksanya.

Metode yang "benar" akan ditentukan selama runtime. Kompilator tidak dapat mengetahui apakah ada metode yang valid di sana selama runtime.

Kata kunci "dinamis" ditentukan untuk bahasa dinamis dan skrip, di mana Metode dapat ditentukan kapan saja, bahkan selama waktu proses. Hal gila

Di sini contoh yang menangani int tetapi tidak ada string, karena metode tersebut ada di instance.

class Program {
    static void Main(string[] args) {
        Example.Foo(1234);
        Example.Foo("1234");
    }
}
public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

Anda dapat menambahkan metode untuk menangani semua panggilan "salah", yang tidak dapat ditangani

public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar<T>(T a) {
        Console.WriteLine("Error handling:" + a);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}
oberfreak
sumber
Bukankah seharusnya kode panggilan dalam contoh Anda adalah Example.Bar (...) daripada Example.Foo (...)? Bukankah Foo () tidak relevan dalam contoh Anda? Saya tidak begitu mengerti teladan Anda. Mengapa menambahkan metode generik statis menyebabkan masalah? Bisakah Anda mengedit jawaban Anda untuk memasukkan metode itu alih-alih memberikannya sebagai opsi?
Mike Scott
tetapi contoh yang saya posting hanya memiliki satu metode contoh dan tidak ada kelebihan beban sehingga pada waktu kompilasi Anda tahu bahwa tidak ada metode statis yang mungkin dapat diselesaikan. Hanya jika Anda menambahkan setidaknya satu situasi berubah dan kodenya valid.
Mike Scott
Tetapi contoh ini masih memiliki lebih dari satu metode Bar (). Contoh saya hanya memiliki satu metode. Jadi tidak ada kemungkinan untuk memanggil metode Bar () statis. Panggilan tersebut dapat diselesaikan pada waktu kompilasi.
Mike Scott
@Mike bisa! = Adalah; dengan dinamika, tidak perlu melakukannya
Marc Gravell
@ MarcGravell, mohon klarifikasi?
Mike Scott