Peringatan: Pertanyaan ini agak sesat ... programmer agama selalu taat pada praktik yang baik, tolong jangan membacanya. :)
Adakah yang tahu mengapa menggunakan TypedReference sangat tidak disarankan (secara implisit, karena kurangnya dokumentasi)?
Saya telah menemukan kegunaan besar untuk itu, seperti ketika melewati parameter generik melalui fungsi yang seharusnya tidak generik (saat menggunakan object
mungkin berlebihan atau lambat, jika Anda membutuhkan tipe nilai), untuk saat Anda membutuhkan pointer buram, atau ketika Anda perlu mengakses elemen array dengan cepat, yang spesifikasinya Anda temukan saat runtime (menggunakan Array.InternalGetReference
). Karena CLR bahkan tidak mengizinkan penggunaan yang salah dari jenis ini, mengapa itu tidak disarankan? Sepertinya tidak aman atau apa pun ...
Kegunaan lain yang saya temukan untuk TypedReference
:
"Mengkhususkan" obat generik dalam C # (ini aman untuk jenis):
static void foo<T>(ref T value)
{
//This is the ONLY way to treat value as int, without boxing/unboxing objects
if (value is int)
{ __refvalue(__makeref(value), int) = 1; }
else { value = default(T); }
}
Menulis kode yang berfungsi dengan pointer umum (ini sangat tidak aman jika disalahgunakan, tetapi cepat dan aman jika digunakan dengan benar):
//This bypasses the restriction that you can't have a pointer to T,
//letting you write very high-performance generic code.
//It's dangerous if you don't know what you're doing, but very worth if you do.
static T Read<T>(IntPtr address)
{
var obj = default(T);
var tr = __makeref(obj);
//This is equivalent to shooting yourself in the foot
//but it's the only high-perf solution in some cases
//it sets the first field of the TypedReference (which is a pointer)
//to the address you give it, then it dereferences the value.
//Better be 10000% sure that your type T is unmanaged/blittable...
unsafe { *(IntPtr*)(&tr) = address; }
return __refvalue(tr, T);
}
Menulis versi metodesizeof
instruksi, yang kadang-kadang berguna:
static class ArrayOfTwoElements<T> { static readonly Value = new T[2]; }
static uint SizeOf<T>()
{
unsafe
{
TypedReference
elem1 = __makeref(ArrayOfTwoElements<T>.Value[0] ),
elem2 = __makeref(ArrayOfTwoElements<T>.Value[1] );
unsafe
{ return (uint)((byte*)*(IntPtr*)(&elem2) - (byte*)*(IntPtr*)(&elem1)); }
}
}
Menulis metode yang melewati parameter "status" yang ingin menghindari tinju:
static void call(Action<int, TypedReference> action, TypedReference state)
{
//Note: I could've said "object" instead of "TypedReference",
//but if I had, then the user would've had to box any value types
try
{
action(0, state);
}
finally { /*Do any cleanup needed*/ }
}
Jadi mengapa penggunaan seperti ini "tidak disarankan" (karena kurangnya dokumentasi)? Adakah alasan keamanan tertentu? Tampaknya sangat aman dan dapat diverifikasi jika tidak dicampur dengan pointer (yang sebenarnya tidak aman atau dapat diverifikasi) ...
Memperbarui:
Kode contoh untuk menunjukkan bahwa, memang, TypedReference
bisa dua kali lebih cepat (atau lebih):
using System;
using System.Collections.Generic;
static class Program
{
static void Set1<T>(T[] a, int i, int v)
{ __refvalue(__makeref(a[i]), int) = v; }
static void Set2<T>(T[] a, int i, int v)
{ a[i] = (T)(object)v; }
static void Main(string[] args)
{
var root = new List<object>();
var rand = new Random();
for (int i = 0; i < 1024; i++)
{ root.Add(new byte[rand.Next(1024 * 64)]); }
//The above code is to put just a bit of pressure on the GC
var arr = new int[5];
int start;
const int COUNT = 40000000;
start = Environment.TickCount;
for (int i = 0; i < COUNT; i++)
{ Set1(arr, 0, i); }
Console.WriteLine("Using TypedReference: {0} ticks",
Environment.TickCount - start);
start = Environment.TickCount;
for (int i = 0; i < COUNT; i++)
{ Set2(arr, 0, i); }
Console.WriteLine("Using boxing/unboxing: {0} ticks",
Environment.TickCount - start);
//Output Using TypedReference: 156 ticks
//Output Using boxing/unboxing: 484 ticks
}
}
(Sunting: Saya mengedit patokan di atas, karena versi terakhir dari pos menggunakan versi debug kode [saya lupa mengubahnya untuk melepaskan], dan tidak memberi tekanan pada GC. Versi ini sedikit lebih realistis, dan pada sistem saya, ini lebih dari tiga kali lebih cepat dengan TypedReference
rata-rata.)
sumber
TypedReference: 203 ticks
,boxing/unboxing: 31 ticks
. Tidak peduli apa yang saya coba (termasuk berbagai cara untuk melakukan penghitungan waktu) tinju / unboxing masih lebih cepat di sistem saya.int
->DockStyle
). Kotak ini nyata, dan hampir sepuluh kali lebih lambat.Jawaban:
Jawaban singkat: portabilitas .
Sementara
__arglist
,__makeref
, dan__refvalue
yang ekstensi bahasa dan tak berdokumen di C # Language Specification, konstruksi digunakan untuk menerapkannya di bawah tenda (vararg
konvensi pemanggilan,TypedReference
jenis,arglist
,refanytype
,mkanyref
, danrefanyval
petunjuk) yang sempurna didokumentasikan dalam CLI Keterangan (ECMA-335) di yang perpustakaan vararg .Didefinisikan di Perpustakaan Vararg membuatnya sangat jelas bahwa mereka terutama dimaksudkan untuk mendukung daftar argumen panjang variabel dan tidak banyak lagi. Daftar argumen-variabel memiliki sedikit kegunaan dalam platform yang tidak perlu berinteraksi dengan kode C eksternal yang menggunakan varargs. Untuk alasan ini, pustaka Varargs bukan bagian dari profil CLI mana pun. Implementasi CLI yang sah dapat memilih untuk tidak mendukung pustaka Varargs karena itu tidak termasuk dalam profil Kernel CLI:
Perbarui (balas ke
GetValueDirect
komentar):FieldInfo.GetValueDirect
adalahFieldInfo.SetValueDirect
yang tidak bagian dari Base Class Library. Perhatikan bahwa ada perbedaan antara .NET Framework Class Library dan Base Class Library. BCL adalah satu-satunya hal yang diperlukan untuk implementasi CLI / C # yang sesuai dan didokumentasikan dalam ECMA TR / 84 . (Sebenarnya,FieldInfo
itu sendiri adalah bagian dari perpustakaan Reflection dan itu tidak termasuk dalam profil Kernel CLI juga).Segera setelah Anda menggunakan metode di luar BCL, Anda memberikan sedikit portabilitas (dan ini menjadi semakin penting dengan munculnya implementasi CLI non-.NET seperti Silverlight dan MonoTouch). Bahkan jika suatu implementasi ingin meningkatkan kompatibilitas dengan Microsoft .NET Framework Class Library, ia dapat dengan mudah menyediakan
GetValueDirect
danSetValueDirect
mengambilTypedReference
tanpa membuatTypedReference
penanganan khusus oleh runtime (pada dasarnya, membuat mereka setara denganobject
rekan - rekan mereka tanpa manfaat kinerja).Jika mereka mendokumentasikannya dalam C #, itu akan memiliki setidaknya beberapa implikasi:
sumber
FieldInfo.GetValueDirect
danFieldInfo.SetValueDirect
? Mereka adalah bagian dari BCL, dan untuk menggunakannya Anda perluTypedReference
, jadi bukankah itu pada dasarnya memaksaTypedReference
untuk selalu didefinisikan, terlepas dari spesifikasi bahasa? (Juga, catatan lain: Sekalipun kata kunci tidak ada, selama instruksinya ada, Anda masih bisa mengaksesnya dengan metode yang memancarkan secara dinamis ... jadi selama platform Anda berhenti dengan pustaka C, Anda dapat menggunakan ini, apakah C # memiliki kata kunci atau tidak.)TypedReference
didokumentasikan hanya untuk satu bahasa - katakanlah, dikelola C ++ - tetapi jika tidak ada bahasa mendokumentasikannya dan jadi jika tidak ada yang benar-benar dapat menggunakannya, lalu mengapa bahkan repot mendefinisikan fitur?)[DllImport("...")] void Foo(__arglist);
) dan mereka menerapkannya dalam C # untuk penggunaan mereka sendiri. Desain CLI dipengaruhi oleh banyak bahasa (anotasi "The Common Language Infrastructure Annotated Standard" menunjukkan fakta ini.) Menjadi runtime yang cocok untuk sebanyak mungkin bahasa, termasuk yang tidak terduga, jelas telah menjadi tujuan desain (oleh karena itu, nama) dan ini adalah fitur yang, misalnya, implementasi C yang dikelola secara hipotesis mungkin dapat diuntungkan.Yah, saya bukan Eric Lippert, jadi saya tidak dapat berbicara langsung tentang motivasi Microsoft, tetapi jika saya berani menebak, saya akan mengatakan itu
TypedReference
dkk. tidak didokumentasikan dengan baik karena, terus terang, Anda tidak membutuhkannya.Setiap penggunaan yang Anda sebutkan untuk fitur-fitur ini dapat diselesaikan tanpa fitur-fitur tersebut, meskipun pada penalti kinerja dalam beberapa kasus. Tetapi C # (dan .NET secara umum) tidak dirancang untuk menjadi bahasa kinerja tinggi. (Saya menduga bahwa "lebih cepat dari Jawa" adalah tujuan kinerja.)
Itu bukan untuk mengatakan bahwa pertimbangan kinerja tertentu belum diberikan. Memang, fitur seperti pointer,,
stackalloc
dan fungsi kerangka kerja tertentu yang dioptimalkan ada sebagian besar untuk meningkatkan kinerja dalam situasi tertentu.Obat generik, yang menurut saya memiliki manfaat utama dari keamanan tipe, juga meningkatkan kinerja seperti halnya
TypedReference
menghindari tinju dan unboxing. Bahkan, saya bertanya-tanya mengapa Anda lebih suka ini:untuk ini:
Pertukarannya, seperti yang saya lihat, adalah bahwa yang pertama membutuhkan JIT yang lebih sedikit (dan, yang mengikuti, lebih sedikit memori), sedangkan yang terakhir lebih akrab dan, saya akan berasumsi, sedikit lebih cepat (dengan menghindari penunjuk dereferencing).
Saya akan menelepon
TypedReference
dan detail penerapan teman. Anda telah menunjukkan beberapa kegunaan yang rapi untuk mereka, dan saya pikir mereka layak untuk ditelusuri, tetapi peringatan yang biasanya bergantung pada detail implementasi berlaku — versi berikutnya dapat memecahkan kode Anda.sumber
call()
: Itu karena kodenya tidak selalu begitu kohesif - saya merujuk lebih ke contoh yang lebih mirip dengan ituIAsyncResult.State
, di mana memperkenalkan obat generik tidak akan mudah dilakukan karena tiba-tiba itu akan memperkenalkan obat generik untuk setiap kelas / metode yang terlibat. +1 untuk jawabannya, terutama ... terutama untuk menunjukkan bagian "lebih cepat daripada Java". :]TypedReference
mungkin tidak akan mengalami perubahan yang melanggar dalam waktu dekat, mengingat FieldInfo.SetValueDirect , yang bersifat publik dan mungkin digunakan oleh beberapa pengembang, tergantung padanya. :)TypedReference
salah satu dari mereka. (Sintaksis yang mengerikan dan ketidaknyamanan keseluruhan mendiskualifikasi, dalam pikiran saya, dari kategori nice-to-have.) Saya akan mengatakan itu hal yang baik untuk dimiliki ketika Anda benar-benar perlu memotong beberapa mikrodetik di sana-sini. Yang mengatakan, saya memikirkan beberapa tempat dalam kode saya sendiri yang akan saya lihat sekarang, untuk melihat apakah saya dapat mengoptimalkannya menggunakan teknik yang Anda tunjukkan.TypedReference
s, tetapi IIRC, satu-satunya tempat saya bisa menghindari tinju di suatu tempat adalah dengan elemen array primitif satu dimensi. Manfaat sedikit kecepatan di sini tidak sebanding dengan kompleksitas yang ditambahkan ke seluruh proyek, jadi saya mengambilnya.delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
koleksi tipeT
dapat menyediakan metodeActOnItem<TParam>(int index, ActByRef<T,TParam> proc, ref TParam param)
, tetapi JITter harus membuat versi metode yang berbeda untuk setiap tipe nilaiTParam
. Menggunakan referensi yang diketik akan memungkinkan satu versi metode JITted untuk bekerja dengan semua jenis parameter.Saya tidak tahu apakah judul pertanyaan ini seharusnya sarkastik: Sudah lama diketahui bahwa
TypedReference
sepupu yang dikelola dengan benar, bengkak, jelek dari pointer yang dikelola 'benar', yang terakhir adalah apa yang kita dapatkan dengan C ++ / CLIinterior_ptr<T>
, atau bahkan parameter referensi-tradisional (ref
/out
) dalam C # . Bahkan, cukup sulit untukTypedReference
mencapai bahkan kinerja dasar hanya dengan menggunakan integer untuk mengindeks ulang dari array CLR asli setiap kali.Detail menyedihkan ada di sini , tapi untungnya, tidak ada yang penting sekarang ...
Fitur-fitur bahasa baru ini memberikan dukungan kelas satu yang menonjol dalam C # untuk mendeklarasikan, berbagi, dan memanipulasi
CLR
tipe referensi terkelola -tip dalam situasi yang ditentukan dengan cermat.Pembatasan penggunaan tidak lebih ketat dari apa yang sebelumnya diperlukan untuk
TypedReference
(dan kinerja benar - benar melompat dari yang terburuk ke yang terbaik ), jadi saya tidak melihat ada use case yang tersisa di C # forTypedReference
. Sebagai contoh, sebelumnya tidak ada cara untuk bertahanTypedReference
diGC
heap, jadi hal yang sama berlaku dari pointer yang dikelola atasan sekarang bukan take-away.Dan jelas, matinya
TypedReference
— atau paling tidak penghentiannya yang paling lengkap — berarti membuang__makeref
sampah itu juga.sumber