Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Nota
Artikel ini adalah spesifikasi fitur. Spesifikasi berfungsi sebagai dokumen desain untuk fitur tersebut. Ini termasuk perubahan spesifikasi yang diusulkan, bersama dengan informasi yang diperlukan selama desain dan pengembangan fitur. Artikel ini diterbitkan sampai perubahan spesifikasi yang diusulkan diselesaikan dan dimasukkan dalam spesifikasi ECMA saat ini.
Mungkin ada beberapa perbedaan antara spesifikasi fitur dan implementasi yang selesai. Perbedaan tersebut dicatat dalam catatan terkait rapat desain bahasa (LDM) .
Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .
Ringkasan
Proposal ini menyediakan konstruksi bahasa yang mengekspos opcode IL yang saat ini tidak dapat diakses secara efisien, atau sama sekali tidak bisa diakses, di C# saat ini: ldftn dan calli. Opcode IL ini dapat menjadi penting dalam kode performa tinggi dan pengembang membutuhkan cara yang efisien untuk mengaksesnya.
Motivasi
Motivasi dan latar belakang untuk fitur ini dijelaskan dalam isu berikut (termasuk potensi implementasi fitur):
Ini adalah proposal desain alternatif untuk intrinsik kompilator
Desain Terperinci
Penunjuk fungsi
Bahasa akan memungkinkan deklarasi penunjuk fungsi menggunakan sintaks delegate*. Sintaks lengkap dijelaskan secara rinci di bagian berikutnya tetapi dimaksudkan untuk menyerupai sintaks yang digunakan oleh deklarasi jenis Func dan Action.
unsafe class Example
{
void M(Action<int> a, delegate*<int, void> f)
{
a(42);
f(42);
}
}
Jenis ini diwakili menggunakan jenis penunjuk fungsi seperti yang diuraikan dalam ECMA-335. Ini berarti pemanggilan delegate* akan menggunakan calli di mana pemanggilan delegate akan menggunakan callvirt pada metode Invoke.
Meskipun secara sintaksis pemanggilan identik untuk kedua konstruksi.
Definisi ECMA-335 dari penunjuk metode mencakup konvensi panggilan sebagai bagian dari tanda tangan jenis (bagian 7.1).
Konvensi panggilan default akan managed. Konvensi pemanggilan yang tidak dikelola dapat ditentukan dengan menempatkan kata kunci unmanaged setelah sintaks delegate*, yang akan menggunakan default platform runtime. Konvensi tertentu yang tidak dikelola kemudian dapat ditentukan dalam tanda kurung ke kata kunci unmanaged dengan menentukan jenis apa pun yang dimulai dengan CallConv di namespace System.Runtime.CompilerServices, meninggalkan awalan CallConv. Jenis-jenis ini harus berasal dari pustaka inti program, dan serangkaian kombinasi yang valid bergantung pada platform.
//This method has a managed calling convention. This is the same as leaving the managed keyword off.
delegate* managed<int, int>;
// This method will be invoked using whatever the default unmanaged calling convention on the runtime
// platform is. This is platform and architecture dependent and is determined by the CLR at runtime.
delegate* unmanaged<int, int>;
// This method will be invoked using the cdecl calling convention
// Cdecl maps to System.Runtime.CompilerServices.CallConvCdecl
delegate* unmanaged[Cdecl] <int, int>;
// This method will be invoked using the stdcall calling convention, and suppresses GC transition
// Stdcall maps to System.Runtime.CompilerServices.CallConvStdcall
// SuppressGCTransition maps to System.Runtime.CompilerServices.CallConvSuppressGCTransition
delegate* unmanaged[Stdcall, SuppressGCTransition] <int, int>;
Konversi antara jenis delegate* dilakukan berdasarkan tanda tangannya termasuk konvensi panggilan.
unsafe class Example {
void Conversions() {
delegate*<int, int, int> p1 = ...;
delegate* managed<int, int, int> p2 = ...;
delegate* unmanaged<int, int, int> p3 = ...;
p1 = p2; // okay p1 and p2 have compatible signatures
Console.WriteLine(p2 == p1); // True
p2 = p3; // error: calling conventions are incompatible
}
}
Jenis delegate* adalah jenis penunjuk yang berarti memiliki semua kemampuan dan batasan jenis pointer standar:
- Hanya berlaku dalam konteks
unsafe. - Metode yang berisi parameter
delegate*atau jenis pengembalian hanya dapat dipanggil dari konteksunsafe. - Tidak dapat dikonversi ke
object. - Tidak dapat digunakan sebagai argumen generik.
- Dapat secara implisit mengonversi
delegate*kevoid*. - Dapat secara eksplisit mengonversi dari
void*kedelegate*.
Restriksi:
- Atribut kustom tidak dapat diterapkan ke
delegate*atau elemennya. - Parameter
delegate*tidak dapat ditandai sebagaiparams - Jenis
delegate*memiliki semua batasan jenis penunjuk normal. - Aritmatika penunjuk tidak dapat dilakukan secara langsung pada jenis penunjuk fungsi.
Sintaks penunjuk fungsi
Sintaks penunjuk fungsi lengkap diwakili oleh tata bahasa berikut:
pointer_type
: ...
| funcptr_type
;
funcptr_type
: 'delegate' '*' calling_convention_specifier? '<' funcptr_parameter_list funcptr_return_type '>'
;
calling_convention_specifier
: 'managed'
| 'unmanaged' ('[' unmanaged_calling_convention ']')?
;
unmanaged_calling_convention
: 'Cdecl'
| 'Stdcall'
| 'Thiscall'
| 'Fastcall'
| identifier (',' identifier)*
;
funptr_parameter_list
: (funcptr_parameter ',')*
;
funcptr_parameter
: funcptr_parameter_modifier? type
;
funcptr_return_type
: funcptr_return_modifier? return_type
;
funcptr_parameter_modifier
: 'ref'
| 'out'
| 'in'
;
funcptr_return_modifier
: 'ref'
| 'ref readonly'
;
Jika tidak ada calling_convention_specifier yang disediakan, defaultnya adalah managed. Pengodean metadata yang tepat dari calling_convention_specifier dan identifieryang valid dalam unmanaged_calling_convention dibahas dalam Representasi Metadata Konvensi Panggilan.
delegate int Func1(string s);
delegate Func1 Func2(Func1 f);
// Function pointer equivalent without calling convention
delegate*<string, int>;
delegate*<delegate*<string, int>, delegate*<string, int>>;
// Function pointer equivalent with calling convention
delegate* managed<string, int>;
delegate*<delegate* managed<string, int>, delegate*<string, int>>;
Konversi penunjuk fungsi
Dalam konteks tidak aman, kumpulan konversi implisit yang tersedia (Konversi implisit) diperluas untuk menyertakan konversi pointer implisit berikut:
- konversi yang ada - (§23,5)
- Dari funcptr_type
F0ke funcptr_typeF1lain , asalkan semua hal berikut ini benar:-
F0danF1memiliki jumlah parameter yang sama, dan setiap parameterD0ndiF0memiliki pengubahref,out, atauinyang sama dengan parameter yang sesuaiD1ndiF1. - Untuk setiap parameter nilai (parameter tanpa pengubah
ref,out, atauin), konversi identitas, konversi referensi implisit, atau konversi pointer implisit ada dari jenis parameter diF0ke jenis parameter yang sesuai diF1. - Untuk setiap parameter
ref,out, atauin, jenis parameter dalamF0sama dengan jenis parameter yang sesuai diF1. - Jika jenis pengembalian berdasarkan nilai (tidak ada
refatauref readonly), identitas, referensi implisit, atau konversi pointer implisit ada dari jenis pengembalianF1ke jenis pengembalianF0. - Jika jenis pengembalian adalah berdasarkan referensi (
refatauref readonly), maka jenis pengembalian dan pengubahrefuntukF1sama dengan jenis pengembalian dan pengubahrefuntukF0. - Konvensi panggilan
F0sama dengan konvensi panggilanF1.
-
Izinkan referensi ke metode tujuan
Grup metode sekarang akan diizinkan sebagai argumen ke alamat ekspresi. Jenis ekspresi seperti itu akan menjadi delegate* yang memiliki tanda tangan yang setara dari metode target dan konvensi panggilan terkelola:
unsafe class Util {
public static void Log() { }
void Use() {
delegate*<void> ptr1 = &Util.Log;
// Error: type "delegate*<void>" not compatible with "delegate*<int>";
delegate*<int> ptr2 = &Util.Log;
}
}
Dalam konteks tidak aman, metode M kompatibel dengan jenis penunjuk fungsi F jika semua hal berikut ini benar:
-
MdanFmemiliki jumlah parameter yang sama, dan setiap parameter dalamMmemiliki pengubahref,out, atauinyang sama dengan parameter yang sesuai diF. - Untuk setiap parameter nilai (parameter tanpa pengubah
ref,out, atauin), konversi identitas, konversi referensi implisit, atau konversi pointer implisit ada dari jenis parameter diMke jenis parameter yang sesuai diF. - Untuk setiap parameter
ref,out, atauin, jenis parameter dalamMsama dengan jenis parameter yang sesuai diF. - Jika jenis pengembalian berdasarkan nilai (tidak ada
refatauref readonly), identitas, referensi implisit, atau konversi pointer implisit ada dari jenis pengembalianFke jenis pengembalianM. - Jika jenis pengembalian adalah berdasarkan referensi (
refatauref readonly), maka jenis pengembalian dan pengubahrefuntukFsama dengan jenis pengembalian dan pengubahrefuntukM. - Konvensi panggilan
Msama dengan konvensi panggilanF. Ini termasuk bit konvensi panggilan, serta bendera konvensi panggilan apa pun yang ditentukan dalam pengidentifikasi yang tidak dikelola. -
Madalah metode statis.
Dalam konteks tidak aman, konversi implisit ada dari alamat ekspresi yang targetnya adalah grup metode E ke jenis penunjuk fungsi yang kompatibel F jika E berisi setidaknya satu metode yang berlaku dalam bentuk normalnya ke daftar argumen yang dibangun dengan menggunakan jenis parameter dan pengubah F, seperti yang dijelaskan dalam hal berikut.
- Metode tunggal
Mdipilih sesuai dengan pemanggilan metode dalam bentukE(A)dengan modifikasi berikut:- Daftar argumen
Aadalah daftar ekspresi, yang masing-masing diklasifikasikan sebagai variabel dengan jenis dan pengubah (ref,out, atauin) yang sesuai dengan funcptr_parameter_list dariF. - Metode kandidat hanyalah metode yang berlaku dalam bentuk normal mereka, bukan yang berlaku dalam bentuk yang diperluas.
- Metode kandidat adalah hanya metode yang bersifat statis.
- Daftar argumen
- Jika algoritma resolusi kelebihan beban menghasilkan kesalahan, maka terjadi kesalahan waktu kompilasi. Jika tidak, algoritma menghasilkan satu metode terbaik
Mmemiliki jumlah parameter yang sama denganFdan konversi dianggap ada. - Metode yang dipilih
Mharus kompatibel (seperti yang didefinisikan di atas) dengan jenis penunjuk fungsiF. Jika tidak, terjadi kesalahan waktu kompilasi. - Hasil konversi adalah penunjuk fungsi jenis
F.
Ini berarti pengembang dapat bergantung pada aturan resolusi kelebihan beban untuk bekerja bersama dengan alamat operator:
unsafe class Util {
public static void Log() { }
public static void Log(string p1) { }
public static void Log(int i) { }
void Use() {
delegate*<void> a1 = &Log; // Log()
delegate*<int, void> a2 = &Log; // Log(int i)
// Error: ambiguous conversion from method group Log to "void*"
void* v = &Log;
}
}
Alamat operator akan diimplementasikan menggunakan instruksi ldftn.
Pembatasan fitur ini:
- Hanya berlaku untuk metode yang ditandai sebagai
static. - Fungsi lokal non-
statictidak dapat digunakan dalam&. Detail implementasi metode ini sengaja tidak ditentukan oleh bahasa. Ini termasuk apakah mereka statis atau instans serta sintaks persis apa yang mereka gunakan.
Operator pada Jenis Penunjuk Fungsi
Bagian dalam kode tidak aman pada ekspresi dimodifikasi seperti:
Dalam konteks yang tidak aman, beberapa konstruk tersedia untuk digunakan pada semua _pointer_type_s yang bukan _funcptr_type_s:
- Operator
*dapat digunakan untuk melakukan pengindereksian pointer (§23.6.2).- Operator
->dapat digunakan untuk mengakses anggota struct melalui pointer (§23.6.3).- Operator
[]dapat digunakan untuk mengindeks pointer (§23,6,4).- Operator
&dapat digunakan untuk mendapatkan alamat variabel (§23.6.5).- Operator
++dan--dapat digunakan untuk kenaikan dan penunjuk penurunan (§23,6,6).- Operator
+dan-dapat digunakan untuk melakukan aritmatika pointer (§23,6,7).- Operator
==,!=,<,>,<=, dan=>dapat digunakan untuk membandingkan pointer (§23,6,8).- Operator
stackallocdapat digunakan untuk mengalokasikan memori dari tumpukan panggilan (§23,8).- Pernyataan
fixeddapat digunakan untuk memperbaiki variabel untuk sementara waktu sehingga alamatnya dapat diperoleh (§23,7).Dalam konteks yang tidak aman, beberapa struktur tersedia untuk beroperasi pada semua _funcptr_type_s.
- Operator
&dapat digunakan untuk mendapatkan alamat metode statis (Izinkan alamat ke metode target)- Operator
==,!=,<,>,<=, dan=>dapat digunakan untuk membandingkan pointer (§23,6,8).
Selain itu, kami memodifikasi semua bagian dalam Pointers in expressions untuk melarang jenis penunjuk fungsi, kecuali Pointer comparison dan The sizeof operator.
Anggota fungsi yang lebih baik
§12.6.4.3 Anggota fungsi yang lebih baik akan diubah untuk menyertakan baris berikut:
delegate*lebih spesifik daripadavoid*
Ini berarti bahwa dimungkinkan untuk kelebihan beban pada void* dan delegate* dan masih sensib menggunakan alamat operator.
Inferensi Jenis
Dalam kode yang tidak aman, perubahan berikut dilakukan pada algoritma inferensi jenis:
Jenis input
Berikut ini ditambahkan:
Jika
Eadalah grup metode alamat danTadalah jenis penunjuk fungsi, maka semua jenis parameterTadalah jenis inputEdengan jenisT.
Jenis output
Berikut ini ditambahkan:
Jika
Eadalah grup metode pengalamatan danTadalah jenis penunjuk fungsi, maka jenis pengembalianTadalah jenis keluaran dariEdengan jenisT.
Inferensi jenis output
Butir berikut ditambahkan di antara butir 2 dan 3:
- Jika
adalah grup metode alamat dan adalah jenis penunjuk fungsi dengan jenis parameter dan jenis pengembalian , dan resolusi kelebihan beban dengan jenis menghasilkan satu metode dengan jenis pengembalian , maka inferensi terikat lebih rendah dibuat dari ke .
Konversi yang lebih baik dari ekspresi
Sub-poin berikut ditambahkan ke dalam daftar poin 2:
Vadalah tipe pointer fungsidelegate*<V2..Vk, V1>danUadalah tipe pointer fungsidelegate*<U2..Uk, U1>, dan konvensi pemanggilanVidentik denganU, serta refnessViidentik denganUi.
Inferensi batas bawah
Kasus berikut ditambahkan ke poin 3:
Vadalah jenis penunjuk fungsidelegate*<V2..Vk, V1>dan ada jenis penunjuk fungsidelegate*<U2..Uk, U1>sedemikian rupa sehinggaUidentik dengandelegate*<U2..Uk, U1>, dan konvensi panggilanVidentik denganU, dan sifat referensiViidentik denganUi.
Poin inferensi pertama dari Ui ke Vi dimodifikasi menjadi:
- Jika
Ubukan tipe penunjuk fungsi danUitidak diketahui sebagai jenis referensi, atau jikaUadalah jenis penunjuk fungsi danUitidak diketahui sebagai jenis penunjuk fungsi atau jenis referensi, maka inferensi yang tepat dibuat
Kemudian, ditambahkan setelah poin inferensi ke-3 dari Ui ke Vi:
- Jika tidak, jika
Vadalahdelegate*<V2..Vk, V1>maka inferensi tergantung pada parameter ke-i daridelegate*<V2..Vk, V1>:
- Jika V1:
- Jika pengembalian berdasarkan nilai, maka inferensi batas bawah dibuat.
- Jika pengembaliannya adalah dengan referensi, maka inferensi
persis dibuat. - Jika V2..Vk:
- Jika parameter berdasarkan nilai, maka suatu inferensi terikat atas dibuat.
- Jika parameter berdasarkan referensi, maka inferensi yang tepat
dibuat.
Inferensi terikat atas
Kasus berikut ditambahkan ke poin 2:
Uadalah jenis penunjuk fungsidelegate*<U2..Uk, U1>danVadalah jenis penunjuk fungsi yang identik dengandelegate*<V2..Vk, V1>, dan konvensi panggilanUidentik denganV, dan sifat referensiUiidentik denganVi.
Poin inferensi pertama dari Ui ke Vi dimodifikasi menjadi:
- Jika
Ubukan tipe penunjuk fungsi danUitidak diketahui sebagai jenis referensi, atau jikaUadalah jenis penunjuk fungsi danUitidak diketahui sebagai jenis penunjuk fungsi atau jenis referensi, maka inferensi yang tepat dibuat
Kemudian ditambahkan setelah poin inferensi ke-3 dari Ui ke Vi:
- Jika tidak, jika
Uadalahdelegate*<U2..Uk, U1>maka inferensi tergantung pada parameter ke-i daridelegate*<U2..Uk, U1>:
- Jika U1:
- Jika pengembalian berdasarkan nilai, maka inferensi batas atas dibuat.
- Jika pengembaliannya adalah dengan referensi, maka inferensi
persis dibuat. - Jika U2..Uk:
- Jika parameter berdasarkan nilai, maka dibuat inferensi batas bawah .
- Jika parameter berdasarkan referensi, maka inferensi yang tepat
dibuat.
Representasi metadata parameter in, out, dan ref readonly dan jenis pengembalian
Signatur pointer fungsi tidak memiliki letak bendera parameter, jadi kita harus mengodekan apakah parameter dan tipe kembalian adalah in, out, atau ref readonly dengan menggunakan modreqs.
in
Kita menggunakan ulang System.Runtime.InteropServices.InAttribute, diterapkan sebagai modreq ke penentu referensi pada parameter atau jenis pengembalian, yang berarti sebagai berikut:
- Jika diterapkan ke penentu ref parameter, parameter ini diperlakukan sebagai
in. - Jika diterapkan ke penentu ref jenis pengembalian, jenis pengembalian diperlakukan sebagai
ref readonly.
out
Kami menggunakan System.Runtime.InteropServices.OutAttribute, diterapkan sebagai modreq pada penentu ref dari jenis parameter, artinya parameter tersebut adalah parameter out.
Kesalahan
- Ini adalah kesalahan untuk menerapkan
OutAttributesebagai modreq ke tipe pengembalian. - Ini adalah kesalahan untuk menerapkan secara bersamaan
InAttributedanOutAttributesebagai modreq ke jenis parameter. - Jika salah satu ditentukan melalui modopt, maka akan diabaikan.
Representasi Metadata dari Konvensi Panggilan
Konvensi panggilan dikodekan dalam tanda tangan metode dalam metadata dengan kombinasi bendera CallKind dalam tanda tangan dan nol atau lebih modoptdi awal tanda tangan. ECMA-335 saat ini mendeklarasikan elemen berikut dalam bendera CallKind:
CallKind
: default
| unmanaged cdecl
| unmanaged fastcall
| unmanaged thiscall
| unmanaged stdcall
| varargs
;
Dari jumlah tersebut, penunjuk fungsi di C# akan mendukung semua kecuali varargs.
Selain itu, runtime (dan nantinya versi 335) akan diperbarui untuk memasukkan CallKind baru pada platform baru. Saat ini belum memiliki nama formal, namun dokumen ini akan menggunakan unmanaged ext sebagai pengganti untuk format konvensi pemanggilan yang dapat diperluas. Tanpa modopt, unmanaged ext adalah konvensi panggilan default platform, unmanaged tanpa tanda kurung siku.
Memetakan calling_convention_specifier ke CallKind
calling_convention_specifier yang dihilangkan, atau ditentukan sebagai managed, dipetakan kedefaultCallKind. Ini adalah CallKind default dari metode apa pun yang tidak dikaitkan dengan UnmanagedCallersOnly.
C# mengenali 4 pengidentifikasi spesial yang memetakan ke komponen CallKindtak terkelola tertentu yang ada dari ECMA 335. Agar pemetaan ini terjadi, pengidentifikasi ini harus ditentukan sendiri, tanpa pengidentifikasi lain, dan persyaratan ini dikodekan ke dalam spesifikasi untuk unmanaged_calling_conventions. Pengidentifikasi ini adalah Cdecl, Thiscall, Stdcall, dan Fastcall, yang masing-masing sesuai dengan unmanaged cdecl, unmanaged thiscall, unmanaged stdcall, dan unmanaged fastcall. Jika lebih dari satu identifer ditentukan, atau satu identifier bukan dari pengidentifikasi yang dikenali secara khusus, kami melakukan pencarian nama khusus pada pengidentifikasi dengan aturan berikut:
- Kami menambahkan
identifierdengan stringCallConv - Kita hanya melihat jenis yang ditentukan dalam namespace
System.Runtime.CompilerServices. - Kita hanya melihat jenis yang ditentukan dalam pustaka inti aplikasi, yang merupakan pustaka yang mendefinisikan
System.Objectdan tidak memiliki dependensi. - Kami hanya memperhatikan tipe publik.
Jika pencarian berhasil di semua identifieryang ditentukan dalam unmanaged_calling_convention, kami mengodekan CallKind sebagai unmanaged ext, dan mengodekan setiap tipe yang telah diselesaikan dalam kumpulan modoptdi awal tanda tangan penunjuk fungsi. Sebagai catatan, aturan ini berarti bahwa pengguna tidak dapat menambahkan awalan identifierini dengan CallConv, karena itu akan menyebabkan pencarian CallConvCallConvVectorCall.
Saat menafsirkan metadata, pertama-tama kita melihat CallKind. Jika itu adalah apa pun selain unmanaged ext, kami mengabaikan semua modoptpada jenis pengembalian untuk tujuan menentukan konvensi panggilan, dan hanya menggunakan CallKind. Jika CallKind adalah unmanaged ext, kita melihat pada modopts di awal dari jenis penunjuk fungsi, dengan mengambil penyatuan semua jenis yang memenuhi persyaratan berikut:
- ditentukan dalam pustaka inti, yang merupakan pustaka yang tidak mereferensikan pustaka lain dan mendefinisikan
System.Object. - Jenis didefinisikan dalam namespace
System.Runtime.CompilerServices. - Jenis dimulai dengan awalan
CallConv. - Jenisnya adalah publik.
Ini mewakili jenis yang harus ditemukan saat melakukan pencarian pada identifierdalam unmanaged_calling_convention saat menentukan jenis penunjuk fungsi di sumber.
Ini adalah kesalahan untuk mencoba menggunakan penunjuk fungsi dengan CallKindunmanaged ext jika runtime target tidak mendukung fitur tersebut. Ini akan ditentukan dengan mencari keberadaan konstanta System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind. Jika konstanta ini ada, runtime dianggap mendukung fitur tersebut.
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute adalah atribut yang digunakan oleh CLR untuk menunjukkan bahwa metode harus dipanggil dengan konvensi panggilan tertentu. Karena itu, kami memperkenalkan dukungan berikut untuk bekerja dengan atribut :
- Ini adalah kesalahan untuk langsung memanggil metode yang dianotasi dengan atribut ini dari C#. Pengguna harus mendapatkan penunjuk fungsi ke metode lalu memanggil penunjuk tersebut.
- Ini adalah kesalahan untuk menerapkan atribut ke apa pun selain metode statis biasa atau fungsi lokal statis biasa. Pengkompilasi C# akan menandai metode non-statis atau statis non-biasa yang diimpor dari metadata dengan atribut ini sebagai tidak didukung oleh bahasa.
- Ini adalah kesalahan bagi metode yang ditandai dengan atribut memiliki parameter atau tipe pengembalian yang bukan
unmanaged_type. - Ini adalah kesalahan jika metode yang ditandai dengan atribut memiliki parameter jenis, bahkan jika parameter jenis tersebut dibatasi oleh
unmanaged. - Merupakan kesalahan jika sebuah metode dalam jenis generik ditandai dengan atribut.
- Ini adalah kesalahan untuk mengonversi metode yang ditandai dengan atribut ke tipe delegasi.
- Ini adalah kesalahan untuk menentukan tipe apa pun untuk
UnmanagedCallersOnly.CallConvsyang tidak sesuai dengan persyaratan konvensi pemanggilanmodoptdalam metadata.
Saat menentukan konvensi panggilan metode yang ditandai dengan atribut UnmanagedCallersOnly yang valid, pengkompilasi melakukan pemeriksaan berikut pada jenis yang ditentukan dalam properti CallConvs untuk menentukan CallKind dan modoptyang efektif yang harus digunakan untuk menentukan konvensi panggilan:
- Jika tidak ada jenis yang ditentukan,
CallKinddiperlakukan sebagaiunmanaged ext, tanpa konvensi panggilanmodoptdi awal jenis penunjuk fungsi. - Jika ada satu jenis yang ditentukan, dan jenis tersebut diberi nama
CallConvCdecl,CallConvThiscall,CallConvStdcall, atauCallConvFastcall,CallKinddiperlakukan sebagaiunmanaged cdecl,unmanaged thiscall,unmanaged stdcall, atauunmanaged fastcall, masing-masing, tanpa konvensi panggilanmodoptdi awal jenis penunjuk fungsi. - Jika beberapa jenis ditentukan atau jenis tunggal tidak dinamai salah satu jenis yang dipanggil secara khusus di atas,
CallKinddiperlakukan sebagaiunmanaged ext, dengan gabungan jenis yang ditentukan diperlakukan sebagaimodoptpada awal jenis penunjuk fungsi.
Pengkompilasi kemudian melihat pengumpulan CallKind dan modopt yang efektif ini dan menggunakan aturan metadata normal untuk menentukan konvensi panggilan akhir dari jenis penunjuk fungsi.
Buka Pertanyaan
Mendeteksi dukungan runtime untuk unmanaged ext
https://github.com/dotnet/runtime/issues/38135 melacak penambahan flag ini. Bergantung pada umpan balik tinjauan, kami akan menggunakan properti yang ditentukan dalam isu, atau menggunakan kehadiran UnmanagedCallersOnlyAttribute sebagai tanda yang menentukan apakah runtime mendukung unmanaged ext.
Pertimbangan
Mengizinkan metode instans
Proposal dapat diperluas untuk mendukung metode instans dengan memanfaatkan konvensi panggilan CLI EXPLICITTHIS (bernama instance dalam kode C#). Bentuk penunjuk fungsi CLI ini menempatkan parameter this sebagai parameter pertama eksplisit dari sintaks penunjuk fungsi.
unsafe class Instance {
void Use() {
delegate* instance<Instance, string> f = &ToString;
f(this);
}
}
Ini masuk akal tetapi menambahkan beberapa komplikasi pada proposal. Terutama karena penunjuk fungsi yang berbeda dengan konvensi panggilan instance dan managed akan tidak kompatibel meskipun kedua kasus digunakan untuk memanggil metode terkelola dengan tanda tangan C# yang sama. Selain itu, dalam setiap kasus yang dipertimbangkan di mana penting untuk memiliki ini, ada solusi sederhana: gunakan fungsi lokal static.
unsafe class Instance {
void Use() {
static string toString(Instance i) => i.ToString();
delegate*<Instance, string> f = &toString;
f(this);
}
}
Tidak memerlukan pengaturan 'unsafe' saat deklarasi
Alih-alih mengharuskan unsafe pada setiap penggunaan delegate*, hanya memerlukannya pada titik di mana grup metode dikonversi ke delegate*. Di sinilah masalah keamanan inti mulai muncul (mengetahui bahwa rakitan yang mengandung tidak dapat dibongkar saat nilainya masih aktif). Membutuhkan unsafe di lokasi-lokasi lainnya dapat dilihat sebagai berlebihan.
Ini adalah bagaimana desain awalnya dimaksudkan. Tetapi aturan bahasa yang dihasilkan terasa sangat canggung. Tidak mungkin untuk menyembunyikan fakta bahwa ini adalah nilai pointer dan tetap muncul bahkan tanpa kata kunci unsafe. Misalnya konversi ke object tidak dapat diizinkan, itu tidak dapat menjadi anggota class, dll ... Desain C# memerlukan unsafe untuk semua penggunaan pointer dan karenanya desain ini mengikutinya.
Pengembang masih akan mampu menyajikan pembungkus aman
unsafe struct Action {
delegate*<void> _ptr;
Action(delegate*<void> ptr) => _ptr = ptr;
public void Invoke() => _ptr();
}
Menggunakan delegasi
Alih-alih menggunakan elemen sintaks baru, delegate*, cukup gunakan jenis delegate yang sudah ada dengan * yang mengikuti jenis tersebut.
Func<object, object, bool>* ptr = &object.ReferenceEquals;
Penanganan konvensi panggilan dapat dilakukan dengan menambahkan anotasi pada jenis delegate dengan atribut yang menentukan nilai CallingConvention. Kurangnya atribut akan menandakan konvensi panggilan terkelola.
Pengkodean di IL bermasalah. Nilai yang mendasar perlu diwakili sebagai penunjuk namun juga harus:
- Memiliki jenis unik untuk memungkinkan kelebihan beban dengan jenis penunjuk fungsi yang berbeda.
- Memenuhi persyaratan untuk tujuan OHI di seluruh batas perakitan.
Poin terakhir sangat bermasalah. Ini berarti bahwa setiap rakitan yang menggunakan Func<int>* harus mengodekan jenis yang setara dalam metadata walaupun Func<int>* didefinisikan dalam rakitan yang tidak dikendalikannya.
Selain itu jenis lain yang didefinisikan dengan nama System.Func<T> dalam rakitan yang bukan mscorlib harus berbeda dari versi yang ditentukan dalam mscorlib.
Salah satu opsi yang dijelajahi adalah memancarkan penunjuk seperti mod_req(Func<int>) void*. Ini tidak berfungsi karena mod_req tidak dapat mengikat ke TypeSpec dan maka tidak dapat menargetkan instansiasi generik.
Penunjuk fungsi bernama
Sintaks penunjuk fungsi dapat rumit, terutama dalam kasus kompleks seperti penunjuk fungsi berlapis. Daripada membuat pengembang mengetik tanda tangan fungsi setiap kali, bahasa dapat mendukung deklarasi penunjuk fungsi yang dinamai, seperti yang dilakukan dengan delegate.
func* void Action();
unsafe class NamedExample {
void M(Action a) {
a();
}
}
Bagian dari masalah di sini adalah primitif CLI yang mendasar tidak memiliki nama sehingga ini akan murni penemuan C# dan memerlukan sedikit pekerjaan metadata untuk diaktifkan. Itu dapat dilakukan tetapi memerlukan banyak pekerjaan. Ini pada dasarnya mengharuskan C# untuk memiliki pendamping untuk tabel definisi tipe murni untuk nama-nama ini.
Selain itu, ketika argumen untuk penunjuk fungsi bernama diperiksa, kami menemukan bahwa argumen tersebut dapat diterapkan dengan sama baiknya untuk sejumlah skenario lain. Misalnya, akan sama praktisnya untuk mendeklarasikan tuple bernama demi mengurangi kebutuhan mengetikkan tanda lengkap dalam semua kasus.
(int x, int y) Point;
class NamedTupleExample {
void M(Point p) {
Console.WriteLine(p.x);
}
}
Setelah diskusi, kami memutuskan untuk tidak mengizinkan deklarasi bernama pada jenis delegate*. Jika kami menemukan ada kebutuhan yang signifikan untuk ini berdasarkan umpan balik dari penggunaan pelanggan, kami akan memeriksa solusi penamaan yang sesuai untuk penunjuk fungsi, tuple, generik, dll. Kemungkinan, ini akan serupa dengan saran lain seperti dukungan penuh untuk typedef dalam bahasa tersebut.
Pertimbangan Masa Depan
delegasi statis
Ini merujuk pada proposal untuk memungkinkan deklarasi jenis delegate yang hanya dapat merujuk pada anggota static. Keuntungannya adalah bahwa instans delegate tersebut dapat menjadi bebas alokasi dan lebih baik dalam skenario sensitif performa.
Jika fitur penunjuk fungsi diimplementasikan, proposal static delegate kemungkinan akan ditutup. Keuntungan yang diusulkan dari fitur itu adalah sifat bebas alokasi. Namun, penyelidikan baru-baru ini menemukan bahwa hal tersebut tidak mungkin dicapai karena pembongkaran proses perakitan. Harus ada handel yang kuat dari static delegate ke metode yang dirujuknya untuk menjaga perakitan agar tidak dibongkar dari bawahnya.
Untuk mempertahankan setiap instans static delegate diperlukan untuk mengalokasikan handle baru yang bertentangan dengan tujuan proposal. Ada beberapa desain di mana alokasi dapat diamortisasi ke satu alokasi per lokasi panggilan tetapi itu agak kompleks dan tampaknya tidak sepadan dengan pertukaran yang harus diterima.
Itu berarti pengembang pada dasarnya harus memutuskan antara trade off berikut:
- Keamanan dalam menghadapi pembongkaran rakitan: ini membutuhkan alokasi dan karenanya
delegatesudah menjadi pilihan yang memadai. - Tidak ada jaminan keamanan saat pembongkaran rakitan: gunakanlah
delegate*. Ini dapat dibungkus dalamstructuntuk memungkinkan penggunaan di luar konteksunsafedalam kode lainnya.
C# feature specifications