Apa yang dapat Anda lakukan di MSIL yang tidak dapat Anda lakukan di C # atau VB.NET? [Tutup]


165

Semua kode yang ditulis dalam bahasa .NET mengkompilasi ke MSIL, tetapi apakah ada tugas / operasi khusus yang dapat Anda lakukan hanya menggunakan MSIL secara langsung?

Mari kita juga melakukan hal-hal yang lebih mudah dalam MSIL daripada C #, VB.NET, F #, j # atau bahasa .NET lainnya.

Sejauh ini kita punya ini:

  1. Rekursi ekor
  2. Co Generik / Kontravarian
  3. Kelebihan yang hanya berbeda dalam jenis pengembalian
  4. Timpa pengubah akses
  5. Memiliki kelas yang tidak dapat diwarisi dari System.Object
  6. Pengecualian yang difilter (dapat dilakukan di vb.net)
  7. Memanggil metode virtual dari tipe kelas statis saat ini.
  8. Dapatkan pegangan pada versi kotak dari tipe nilai.
  9. Lakukan percobaan / kesalahan.
  10. Penggunaan nama terlarang.
  11. Tentukan konstruktor tanpa parameter Anda sendiri untuk tipe nilai .
  12. Tentukan acara dengan raiseelemen.
  13. Beberapa konversi diizinkan oleh CLR tetapi tidak oleh C #.
  14. Jadikan main()metode non sebagai .entrypoint.
  15. bekerja dengan jenis asli intdan asli unsigned intsecara langsung.
  16. Bermain dengan pointer sementara
  17. direktif emitbyte di MethodBodyItem
  18. Lempar dan tangkap bukan tipe System.Exception
  19. Warisan Enum (Tidak Diverifikasi)
  20. Anda dapat memperlakukan array byte sebagai array int (4x lebih kecil).
  21. Anda dapat memiliki bidang / metode / properti / acara semua memiliki nama yang sama (Tidak Diverifikasi).
  22. Anda bisa bercabang kembali menjadi blok percobaan dari blok tangkapnya sendiri.
  23. Anda memiliki akses ke specifier akses famandassem ( protected internalis fam or assem)
  24. Akses langsung ke <Module>kelas untuk mendefinisikan fungsi global, atau inisialisasi modul.

17
Pertanyaan yang sangat bagus!
Tamas Czinege

5
F # tidak mendukung rekursi ekor, lihat: en.wikibooks.org/wiki/F_Sharp_Programming/Recursion
Bas Bossink

4
Mewarisi enum? Terkadang akan sangat menyenangkan ..
Jimmy Hoffa 3-10

1
Metode Utama memiliki huruf M dalam .NET
Concrete Gannet

4
Klaim "tertutup sebagai tidak konstruktif" tidak masuk akal. Ini adalah pertanyaan empiris.
Jim Balter

Jawaban:


34

MSIL memungkinkan untuk kelebihan yang hanya berbeda dalam jenis pengembalian karena

call void [mscorlib]System.Console::Write(string)

atau

callvirt int32 ...

5
Bagaimana kamu tahu hal semacam ini? :)
Gerrie Schenck


8
Ini luar biasa. Siapa yang tidak ingin membuat kelebihan pengembalian?
Jimmy Hoffa

12
Jika dua metode identik kecuali untuk tipe pengembalian, dapatkah dipanggil dari C # atau vb.net?
supercat

29

Sebagian besar bahasa .Net termasuk C # dan VB tidak menggunakan fitur rekursi ekor dari kode MSIL.

Rekursi ekor adalah optimisasi yang umum dalam bahasa fungsional. Itu terjadi ketika metode A berakhir dengan mengembalikan nilai metode B sedemikian rupa sehingga tumpukan metode A dapat dibatalkan alokasi begitu panggilan ke metode B dibuat.

Kode MSIL mendukung rekursi ekor secara eksplisit, dan untuk beberapa algoritma ini bisa menjadi optimasi penting untuk dilakukan. Tetapi karena C # dan VB tidak menghasilkan instruksi untuk melakukan ini, itu harus dilakukan secara manual (atau menggunakan F # atau bahasa lain).

Berikut adalah contoh bagaimana rekursi ekor dapat diimplementasikan secara manual dalam C #:

private static int RecursiveMethod(int myParameter)
{
    // Body of recursive method
    if (BaseCase(details))
        return result;
    // ...

    return RecursiveMethod(modifiedParameter);
}

// Is transformed into:

private static int RecursiveMethod(int myParameter)
{
    while (true)
    {
        // Body of recursive method
        if (BaseCase(details))
            return result;
        // ...

        myParameter = modifiedParameter;
    }
}

Ini adalah praktik umum untuk menghapus rekursi dengan memindahkan data lokal dari tumpukan perangkat keras ke struktur data tumpukan yang dialokasikan tumpukan. Dalam penghapusan rekursi ekor-panggilan seperti yang ditunjukkan di atas, tumpukan dihilangkan sepenuhnya, yang merupakan optimasi yang cukup bagus. Juga, nilai kembali tidak harus berjalan lama, tetapi dikembalikan langsung.

Tetapi, bagaimanapun, CIL menyediakan fitur ini sebagai bagian dari bahasa, tetapi dengan C # atau VB harus diimplementasikan secara manual. (Jitter juga bebas untuk membuat pengoptimalan ini sendiri, tetapi itu adalah masalah lain.)


1
F # tidak menggunakan rekursi ekor MSIL, karena hanya bekerja dalam kasus yang sepenuhnya tepercaya (CAS) karena caranya tidak meninggalkan tumpukan untuk memeriksa asumsi izin (dll.).
Richard

10
Richard, aku tidak yakin apa maksudmu. F # tentu memancarkan ekor. panggilan awalan, hampir di semua tempat. Periksa IL untuk ini: "let print x = print_any x".
MichaelGG

1
Saya percaya bahwa JIT akan tetap menggunakan rekursi ekor dalam beberapa kasus - dan dalam beberapa kasus akan mengabaikan permintaan eksplisit untuk itu. Itu tergantung pada arsitektur prosesor, IIRC.
Jon Skeet

3
@ Bel: Sementara arsitektur prosesor tidak relevan dalam teori , itu tidak tidak relevan dalam praktik karena JIT yang berbeda untuk arsitektur yang berbeda memiliki aturan yang berbeda untuk rekursi ekor di .NET. Dengan kata lain, Anda bisa dengan mudah memiliki program yang meledak pada x86 tetapi tidak pada x64. Hanya karena rekursi ekor dapat diimplementasikan dalam kedua kasus tidak berarti itu adalah . Perhatikan bahwa pertanyaan ini khusus tentang .NET.
Jon Skeet

2
C # benar-benar melakukan panggilan ekor di bawah x64 dalam kasus-kasus tertentu: community.bartdesmet.net/blogs/bart/archive/2010/07/07/… .
Pieter van Ginkel

21

Di MSIL, Anda bisa memiliki kelas yang tidak bisa mewarisi dari System.Object.

Kode contoh: kompilasi dengan ilasm.exe PEMBARUAN: Anda harus menggunakan "/ NOAUTOINHERIT" untuk mencegah assembler dari pewarisan otomatis.

// Metadata version: v2.0.50215
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 2:0:0:0
}
.assembly sample
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module sample.exe
// MVID: {A224F460-A049-4A03-9E71-80A36DBBBCD3}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x02F20000


// =============== CLASS MEMBERS DECLARATION ===================

.class public auto ansi beforefieldinit Hello
{
  .method public hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    // Code size       13 (0xd)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldstr      "Hello World!"
    IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000b:  nop
    IL_000c:  ret
  } // end of method Hello::Main
} // end of class Hello

2
@Jon Skeet - Dengan segala hormat, Bisakah Anda membantu saya memahami apa yang dimaksud NOAUTOINHERIT. MSDN menentukan "Menonaktifkan warisan bawaan dari Object ketika tidak ada kelas dasar yang ditentukan. Baru di .NET Framework versi 2.0."
Ramesh

2
@Michael - Pertanyaannya adalah tentang MSIL dan bukan Common Intermediate Language. Saya setuju ini tidak mungkin di CIL tetapi, masih bekerja dengan MSIL
Ramesh

4
@ Ramesh: Ups, Anda memang benar. Saya akan mengatakan pada saat itu melanggar spesifikasi standar, dan tidak boleh digunakan. Reflektor bahkan tidak memuat assmebly. Namun, itu bisa dilakukan dengan ilasm. Saya bertanya-tanya mengapa di bumi itu ada di sana.
Jon Skeet

4
(Ah, saya melihat / noautoinherit bit ditambahkan setelah komentar saya Setidaknya aku merasa agak lebih baik tentang tidak menyadari itu sebelumnya ....)
Jon Skeet

1
Saya akan menambahkan bahwa setidaknya pada. NET 4.5.2 pada Windows kompilasi tetapi tidak menjalankan ( TypeLoadException). PEVerify pengembalian: [MD]: Kesalahan: TypeDef yang bukan Antarmuka dan bukan kelas Objek memperluas token Nil.
xanatos

20

Dimungkinkan untuk menggabungkan protecteddan internalmengakses pengubah. Dalam C #, jika Anda menulis protected internalanggota dapat diakses dari majelis dan dari kelas turunan. Melalui MSIL Anda bisa mendapatkan anggota yang hanya dapat diakses dari kelas turunan dalam majelis saja . (Saya pikir itu bisa sangat berguna!)


4
Ini adalah kandidat yang sekarang akan diimplementasikan dalam C # 7.1 ( github.com/dotnet/csharplang/issues/37 ), pengubah aksesnya adalahprivate protected
Happypig375

5
Ini telah dirilis sebagai bagian dari C # 7.2: blogs.msdn.microsoft.com/dotnet/2017/11/15/…
Joe Sewell

18

Ooh, saya tidak melihat ini saat itu. (Jika Anda menambahkan tag jon-skeet itu lebih cenderung, tapi saya tidak sering memeriksanya.)

Sepertinya Anda sudah mendapat jawaban yang cukup bagus. Sebagai tambahan:

  • Anda tidak bisa mendapatkan pegangan pada versi kotak dari tipe nilai dalam C #. Anda bisa menggunakan C ++ / CLI
  • Anda tidak dapat melakukan percobaan / kesalahan dalam C # ("kesalahan" adalah seperti "tangkap semuanya dan rethrow di akhir blok" atau "akhirnya tetapi hanya pada kegagalan")
  • Ada banyak nama yang dilarang oleh C # tetapi IL legal
  • IL memungkinkan Anda untuk menentukan konstruktor tanpa parameter Anda sendiri untuk tipe nilai .
  • Anda tidak dapat menentukan acara dengan elemen "naikkan" di C #. (Di VB Anda harus ke acara khusus, tetapi acara "default" tidak termasuk satu.)
  • Beberapa konversi diizinkan oleh CLR tetapi tidak oleh C #. Jika Anda masuk objectdalam C #, ini kadang-kadang akan berfungsi. Lihat pertanyaan [] / int [] SO sebagai contoh.

Saya akan menambahkan ini jika saya memikirkan hal lain ...


3
Ah tag jon-skeet, aku tahu aku kehilangan sesuatu!
Binoj Antony

Untuk menggunakan nama pengenal ilegal, Anda dapat
mengawali

4
@ George: Itu berfungsi untuk kata kunci, tetapi tidak semua nama IL yang valid. Coba <>asebutkan sebagai nama dalam C # ...
Jon Skeet


14

Di IL, Anda dapat melempar dan menangkap jenis apa pun, bukan hanya jenis yang berasal System.Exception.


6
Anda dapat melakukannya di C # juga, dengan try/ catchtanpa tanda kurung di catch-statement Anda juga akan menangkap pengecualian yang tidak seperti pengecualian. Melempar, bagaimanapun, memang hanya mungkin ketika Anda mewarisi dari Exception.
Abel

@ Bel Anda hampir tidak bisa mengatakan bahwa Anda menangkap sesuatu jika Anda tidak bisa merujuknya.
Jim Balter

2
@ JimBalter jika Anda tidak menangkapnya, aplikasi macet. Jika Anda menangkapnya, aplikasi tidak macet. Jadi merujuk ke objek pengecualian berbeda dari menangkapnya.
Daniel Earwicker

Lol! Perbedaan antara penghentian atau keberlangsungan aplikasi adalah kecurangan? Sekarang saya pikir saya mungkin sudah mendengar semuanya.
Daniel Earwicker

Cukup menarik, CLR tidak lagi memungkinkan Anda melakukan ini. Secara default ini akan membungkus objek non-pengecualian di RuntimeWrappedException .
Jwosty

10

IL memiliki perbedaan antara calldan callvirtuntuk pemanggilan metode virtual. Dengan menggunakan yang pertama Anda dapat memaksa memanggil metode virtual dari tipe kelas statis saat ini daripada fungsi virtual dalam tipe kelas dinamis.

C # tidak memiliki cara untuk melakukan ini:

abstract class Foo {
    public void F() {
        Console.WriteLine(ToString()); // Always a virtual call!
    }

    public override string ToString() { System.Diagnostics.Debug.Assert(false); }
};

sealed class Bar : Foo {
    public override string ToString() { return "I'm called!"; }
}

VB, seperti IL, dapat mengeluarkan panggilan nonvirtual dengan menggunakan MyClass.Method()sintaks. Di atas, ini akan menjadi MyClass.ToString().


9

Dalam try / catch, Anda dapat memasukkan kembali blok try dari blok catch-nya sendiri. Jadi, Anda bisa melakukan ini:

.try {
    // ...

  MidTry:
    // ...

    leave.s RestOfMethod
}
catch [mscorlib]System.Exception {
    leave.s MidTry  // branching back into try block!
}

RestOfMethod:
    // ...

AFAIK Anda tidak dapat melakukan ini dalam C # atau VB


3
Saya bisa melihat mengapa ini dihilangkan - Ini memiliki bau yang berbeda dariGOTO
Dasar

3
Kedengarannya seperti On Error Resume Selanjutnya di VB.NET
Thomas Weller

1
Ini sebenarnya bisa dilakukan di VB.NET. Pernyataan GoTo dapat bercabang dari CatchkeTry . Jalankan kode uji online di sini .
mbomb007

9

Dengan IL dan VB.NET Anda dapat menambahkan filter saat mendapatkan pengecualian, tetapi C # v3 tidak mendukung fitur ini.

Contoh VB.NET ini diambil dari http://blogs.msdn.com/clrteam/archive/2009/02/05/catch-rethrow-and-filters-why-you-should-care.aspx (perhatikan When ShouldCatch(ex) = Truedi Tangkap klausa):

Try
   Foo()
Catch ex As CustomBaseException When ShouldCatch(ex)
   Console.WriteLine("Caught exception!")
End Try

17
Tolong hapus = True, itu membuat mataku berdarah!
Konrad Rudolph

Mengapa? Ini adalah VB dan bukan C #, jadi tidak ada masalah = / ==. ;-)
peSHIr

Nah c # dapat melakukan "throw;", sehingga hasil yang sama dapat dicapai.
Frank Schwieterman

8
paSHIr, saya percaya dia sedang berbicara tentang redudansi itu
LegendLength

5
@ Frank Schwieterman: Ada perbedaan antara menangkap dan memikirkan kembali pengecualian, dibandingkan menunda menangkapnya. Filter dijalankan sebelum pernyataan "akhirnya" yang disarangkan, sehingga keadaan yang menyebabkan pengecualian akan tetap ada saat filter dijalankan. Jika seseorang mengharapkan sejumlah besar SocketException untuk dilemparkan bahwa seseorang akan ingin menangkap secara relatif diam-diam, tetapi beberapa dari mereka akan menandakan masalah, bisa memeriksa keadaan ketika yang bermasalah dilemparkan bisa sangat berguna.
supercat


7

Native types
Anda dapat bekerja dengan int asli dan tipe int unsigned asli secara langsung (di c # Anda hanya dapat bekerja pada IntPtr yang tidak sama.

Transient Pointers
Anda dapat bermain dengan pointer sementara, yang merupakan pointer ke tipe yang dikelola tetapi dijamin tidak bergerak dalam memori karena mereka tidak ada dalam tumpukan yang dikelola. Tidak sepenuhnya yakin bagaimana Anda bisa menggunakan ini tanpa mengotak-atik kode yang tidak dikelola tetapi tidak terpapar ke bahasa lain secara langsung hanya melalui hal-hal seperti stackalloc.

<Module>
Anda dapat mengacau dengan kelas jika Anda menginginkannya (Anda dapat melakukan ini dengan refleksi tanpa perlu IL)

.emitbyte

15.4.1.1 Arahan .emitbyte MethodBodyItem :: =… | .emitbyte Int32 Arahan ini menyebabkan nilai 8-bit yang tidak ditandatangani dipancarkan langsung ke aliran CIL dari metode ini, pada titik di mana arahan tersebut muncul. [Catatan: Arahan .emitbyte digunakan untuk membuat tes. Tidak diperlukan dalam menghasilkan program reguler. catatan akhir]

.entrypoint
Anda memiliki sedikit lebih banyak fleksibilitas dalam hal ini, Anda dapat menerapkannya pada metode yang tidak disebut Utama misalnya.

membaca spec saya yakin Anda akan menemukan beberapa lagi.


+1, beberapa permata tersembunyi di sini. Catatan yang <Module>dimaksudkan sebagai kelas khusus untuk bahasa yang menerima metode global (seperti yang dilakukan VB), tetapi memang, C # tidak dapat mengaksesnya secara langsung.
Abel

Pointer sementara sepertinya mereka akan menjadi tipe yang sangat berguna; banyak keberatan terhadap struktur yang dapat berubah berasal dari kelalaiannya. Misalnya, "DictOfPoints (kunci) .X = 5;" akan bisa diterapkan jika DictOfPoints (kunci) mengembalikan pointer sementara ke struct, daripada menyalin struct dengan nilai.
supercat

@ supercat yang tidak akan berfungsi dengan penunjuk sementara, data yang dimaksud bisa di heap. apa yang Anda inginkan adalah ref mengembalikan yang dibicarakan oleh Eric di sini: blogs.msdn.com/b/ericlippert/archive/2011/06/23/…
ShuggyCoUk

@ShuggyCoUk: Menarik. Saya benar-benar berharap Eric dapat dibujuk untuk menyediakan sarana yang dengannya "DictOfPoints (key) .X = 5;" bisa dibuat bekerja. Saat ini jika seseorang ingin kode-keras DictOfPoints bekerja secara eksklusif dengan tipe Point (atau tipe tertentu lainnya), kita hampir dapat membuatnya bekerja, tetapi itu menyebalkan. BTW, satu hal yang saya ingin lihat akan menjadi sarana di mana orang dapat menulis fungsi generik terbuka misalnya DoStuff <...> (someParams, ActionByRef <moreParams, ...>, ...) yang dapat berkembang sesuai kebutuhan. Saya kira akan ada beberapa cara untuk melakukannya di MSIL; dengan bantuan kompiler ...
supercat

@ShuggyCoUk: ... ini akan memberikan cara lain untuk memiliki properti referensi tambahan, dengan bonus tambahan yang dapat dijalankan beberapa properti setelah semua yang dilakukan orang luar terhadap referensi telah dilakukan.
supercat

6

Anda dapat meretas metode override co / contra-variance, yang tidak diizinkan oleh C # (ini BUKAN sama dengan varian umum!). Saya mendapat informasi lebih lanjut tentang penerapan ini di sini , dan bagian 1 dan 2


4

Saya pikir yang saya terus berharap (dengan alasan yang sepenuhnya salah) adalah warisan di Enums. Sepertinya bukan hal yang sulit untuk dilakukan di SMIL (karena Enums hanyalah kelas) tetapi itu bukan sesuatu yang sintaks C # ingin Anda lakukan.


4

Inilah beberapa lagi:

  1. Anda dapat memiliki metode contoh tambahan dalam delegasi.
  2. Delegasi dapat mengimplementasikan antarmuka.
  3. Anda dapat memiliki anggota statis dalam delegasi dan antarmuka.

3

20) Anda dapat memperlakukan array byte sebagai array int (4x lebih kecil).

Saya menggunakan ini baru-baru ini untuk melakukan implementasi XOR cepat, karena fungsi CLR xor beroperasi pada int dan saya perlu melakukan XOR pada aliran byte.

Kode yang dihasilkan diukur menjadi ~ 10x lebih cepat dari yang dilakukan di C # (melakukan XOR pada setiap byte).

===

Saya tidak punya cukup stackoverflow street credz untuk mengedit pertanyaan dan menambahkan ini ke daftar sebagai # 20, jika orang lain bisa membengkak ;-)


3
Daripada mencelupkan ke dalam IL, Anda bisa melakukannya dengan petunjuk yang tidak aman. Saya membayangkan itu akan sama cepat, dan mungkin lebih cepat, karena tidak ada batas memeriksa.
P Ayah

3

Obfuscator sesuatu menggunakan - Anda dapat memiliki bidang / metode / properti / acara semua memiliki nama yang sama.


1
Saya menaruh sampel di situs saya: jasonhaley.com/files/NameTestA.zip Dalam zip itu ada IL dan exe yang berisi kelas dengan 'A' berikut: nama kelas -A adalah -Acara bernama A -Metode bernama A -Properti bernama A -2 Fields bernama AI tidak dapat menemukan referensi yang bagus untuk menunjukkannya padamu, meskipun aku mungkin membacanya baik dalam spesifikasi ecma 335 atau buku Serge Lidin.
Jason Haley

2

Warisan enum tidak benar-benar mungkin:

Anda bisa mewarisi dari kelas Enum. Tetapi hasilnya tidak berperilaku seperti Enum pada khususnya. Bahkan berperilaku tidak seperti tipe nilai, tetapi seperti kelas biasa. Yang srange adalah: IsEnum: True, IsValueType: True, IsClass: False

Tapi itu tidak terlalu berguna (kecuali jika Anda ingin membingungkan seseorang atau runtime itu sendiri.)


2

Anda juga bisa mendapatkan kelas dari delegasi System.Multicast di IL, tetapi Anda tidak bisa melakukan ini di C #:

// Definisi kelas berikut ini ilegal:

kelas publik YourCustomDelegate: MulticastDelegate {}


1

Anda juga dapat mendefinisikan metode tingkat modul (alias global) di IL, dan C #, sebaliknya, hanya memungkinkan Anda untuk menentukan metode selama metode tersebut melekat pada setidaknya satu jenis.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.