Hai Saya ingin menunjukkan kepada Anda contoh menggunakan
StructLayout untuk sesuatu yang lebih menarik daripada contoh dengan byte, int, dan tipe primitif lainnya, ketika semuanya terjadi dengan sangat jelas.
Sebelum melanjutkan ke pelanggaran enkapsulasi kilat-cepat, ada baiknya mengingat secara singkat apa itu StructLayout. Sebenarnya, itu bahkan
StructLayoutAttribute , atribut yang memungkinkan Anda untuk membuat struktur dan kelas yang mirip dengan penyatuan di C ++. Atribut ini memungkinkan Anda untuk mengontrol penempatan anggota kelas dalam memori (menggunakan offset). Dengan demikian, ditempatkan di atas kelas.
Biasanya, jika kelas memiliki 2 bidang, kami berharap mereka akan diatur secara berurutan, yaitu mereka akan independen satu sama lain (jangan tumpang tindih). Namun,
StructLayout memungkinkan Anda menentukan bahwa lokasi bidang tidak akan ditetapkan oleh lingkungan, tetapi oleh pengguna. Untuk menentukan offset bidang secara eksplisit, kita harus menggunakan parameter
LayoutKind.Explicit .
Untuk menunjukkan offset mana dari awal kelas / struktur (selanjutnya hanya "kelas") yang ingin kita tempatkan field, kita perlu meletakkan atribut
FieldOffset di atasnya. Atribut ini sebagai parameter jumlah byte - ofset dari awal kelas. Tidak mungkin untuk melewatkan nilai negatif, agar tidak merusak pointer ke tabel metode atau indeks blok sinkronisasi. Jadi itu akan menjadi sedikit lebih rumit.
Mari kita mulai menulis kode. Untuk mulai dengan, saya sarankan memulai dengan contoh sederhana. Buat kelas berikut:
public class CustomClass { public override string ToString() { return "CUSTOM"; } public virtual object Field { get; } = new object(); }
Selanjutnya, kami menggunakan mekanisme yang dijelaskan di atas untuk menentukan offset bidang secara eksplisit.
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClass SomeInstance; }
Untuk saat ini, saya akan menunda penjelasan dan menggunakan kelas tertulis sebagai berikut:
class Program { static void Main(string[] args) { CustomStructWithLayout instance = new CustomStructWithLayout(); instance.SomeInstance = new CustomClass(); instance.Str = "4564"; Console.WriteLine(instance.SomeInstance.GetType()); //System.String Console.WriteLine(instance.SomeInstance.ToString()); //4564 Console.Read(); } }
Memanggil metode
GetType () mengembalikan sebuah string, metode
ToString () adalah nakal dan memberi kita string "4564".
Brain Discharge: Apa yang akan ditampilkan setelah memanggil properti virtual
CustomClass ?
Seperti yang sudah Anda tebak, kami menginisialisasi
CustomStructWithLayout , kedua tautan adalah nol, lalu kami menginisialisasi bidang tipe kami, dan kemudian menetapkan string ke bidang Str. Akibatnya, tautan
CustomClass tidak menunjuk ke objek
CustomClass , itu menunjuk ke objek System.string (termasuk tabel metode dan indeks unit sinkronisasi). Tetapi kompiler melihat bidang masih dari jenis kelas kami.
Sebagai bukti, ini adalah kliping kecil dari WinDbg:

Di sini Anda dapat melihat beberapa hal yang tidak biasa.
- Dalam bidang objek CustomStructWithLayout memiliki alamat yang berbeda dari tabel metode (sangat diharapkan), tetapi alamat objek adalah sama.
- Yang kedua adalah bahwa Anda dapat melihat bahwa kedua bidang terletak di offset 4. Saya pikir sebagian besar akan mengerti, tetapi untuk berjaga-jaga, saya akan menjelaskan, langsung ke alamat objek menempatkan tautan ke tabel metode. Bidang dimulai dengan offset 4 byte (untuk 32 bit), dan indeks blok sinkronisasi terletak dengan offset -4. Dengan demikian, kedua objek berada pada offset yang sama.
Sekarang Anda telah mengetahui apa yang terjadi, Anda dapat mencoba menggunakan offset untuk memanggil apa yang seharusnya tidak dipanggil.
Untuk ini, saya mengulangi struktur kelas string di salah satu kelas saya. Tetapi saya mengulangi hanya permulaan, karena string kelas cukup banyak dan saya sangat malas.
Catatan: const dan statika tidak diperlukan, hanya untuk bersenang-senang.
public class CustomClassLikeString { public const int FakeAlignConst = 3; public const int FakeCharPtrAlignConst = 3; public static readonly object FakeStringEmpty; public char FakeFirstChar; public int FakeLength = 3; public const int FakeTrimBoth = 3; public const int FakeTrimHead = 3; public const int FakeTrimTail = 3; public CustomClassLikeString(){} public CustomClassLikeString(int a){} public CustomClassLikeString(byte a){} public CustomClassLikeString(short a){} public CustomClassLikeString(string a){} public CustomClassLikeString(uint a){} public CustomClassLikeString(ushort a){} public CustomClassLikeString(long a){ } public void Stub1() { } public virtual int CompareTo(object value) { return 800; } public virtual int CompareTo(string value) { return 801; } }
Nah, struktur dengan tata letak akan sedikit berubah.
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClassLikeString SomeInstance; }
Lebih lanjut, ketika memanggil
FakeLength atau metode
CompareTo () , karena offset yang identik dari anggota kelas ini relatif terhadap alamat objek itu sendiri, metode string yang sesuai akan dipanggil (dalam kasus ini).
Memperoleh metode pribadi pertama dari string yang dapat saya gunakan terlalu lama, jadi saya berhenti di publik. Tapi lapangan itu pribadi, semuanya jujur. By the way, metode yang dibuat virtual untuk melindungi terhadap segala optimasi yang mengganggu pekerjaan (misalnya, embedding), dan juga agar metode ini dipanggil oleh offset dalam tabel metode.
Jadi, kinerja. Jelas bahwa pesaing langsung dalam memanggil hal-hal yang tidak boleh dipanggil dan melanggar enkapsulasi adalah refleksi. Saya pikir jelas bahwa kita lebih cepat daripada hal ini, karena kita tidak menganalisis metadata. Nilai yang tepat:
Metode | Ayub | Berarti | Kesalahan | Stddev | Median |
---|
StructLayoutField | Clr | 0,0597 ns | 0,0344 ns | 0,0396 ns | 0,0498 ns |
ReflectionField | Clr | 197.1257 ns | 1.9148 ns | 1,7911 ns | 197.4787 ns |
StructLayoutMethod | Clr | 3,5195 ns | 0,0382 ns | 0,0319 ns | 3,5285 ns |
Metode Refleksi | Clr | 743.9793 ns | 13.7378 ns | 12.8504 ns | 743.8471 ns |
Berikut ini adalah sepotong kode panjang dengan cara saya mengukur kinerja (Jika seseorang membutuhkannya):
Kode [ClrJob] [RPlotExporter, RankColumn] [InProcessAttribute] public class Benchmarking { private CustomStructWithLayout instance; private string str; [GlobalSetup] public void Setup() { instance = new CustomStructWithLayout(); instance.SomeInstance = new CustomClassLikeString(); instance.Str = "4564"; str = "4564"; } [Benchmark] public int StructLayoutField() { return instance.SomeInstance.FakeLength; } [Benchmark] public int ReflectionField() { return (int)typeof(string).GetField("m_stringLength", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(str); } [Benchmark] public int StructLayoutMethod() { return instance.SomeInstance.CompareTo("4564"); } [Benchmark] public int ReflectionMethod() { return (int)typeof(string).GetMethod("CompareTo", new[] { typeof(string) }).Invoke(str, new[] { "4564" }); } } class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<Benchmarking>(); } }
Versi Rusia