Halo semuanya. Saya ingin membagikan contoh menggunakan StructLayout untuk sesuatu yang lebih menarik daripada contoh dengan byte, int dan angka lainnya, di mana semuanya terjadi sedikit lebih dari yang diharapkan.
Sebelum memulai pelanggaran enkapsulasi kilat-cepat, singkatnya ada baiknya mengingat apa itu StructLayout. Sebenarnya, ini bahkan merupakan StructLayoutAttribute, yaitu atribut yang memungkinkan Anda membuat struktur dan kelas yang mirip dengan penyatuan dalam C ++. Secara lebih rinci, atribut ini memungkinkan Anda untuk mengontrol penempatan anggota kelas dalam memori. 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 untuk menentukan bahwa lokasi bidang akan ditentukan bukan oleh lingkungan, tetapi oleh pengguna. Untuk secara eksplisit menunjukkan offset bidang, gunakan parameter LayoutKind.Explicit. Untuk menunjukkan pada apa offset relatif terhadap awal kelas / struktur (selanjutnya disebut sebagai kelas) kita ingin menempatkan bidang, kita harus meletakkan atribut FieldOffset di atasnya, yang mengambil sebagai parameter jumlah byte - indentasi dari awal kelas. Nilai negatif tidak dapat diteruskan, jadi jangan berpikir tentang memanjakan pointer ke tabel metode atau indeks blok sinkronisasi, itu akan menjadi sedikit lebih rumit.
Mari kita mulai menulis kode. Untuk memulai, saya sarankan memulai dengan contoh sederhana. Buat kelas dengan bentuk berikut:
public class CustomClass { public override string ToString() { return "CUSTOM"; } public virtual object Field { get; } = new object(); }
Selanjutnya, kami menggunakan mekanisme di atas untuk secara eksplisit mengatur offset bidang.
[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(); } }
Total Panggilan ke metode GetType () mengembalikan string, metode ToString () nakal dan memberi kita string "4564".
Debit untuk otak: Apa yang akan ditampilkan ketika CustomClass properti virtual dipanggil?
Seperti yang mungkin sudah Anda duga, kami menginisialisasi CustomStructWithLayout, kedua tautan adalah nol, lalu kami menginisialisasi bidang tipe kami, dan setelah itu kami menetapkan string ke bidang Str. Akibatnya, sedikit lebih banyak yang tersisa dari CustomClass daripada tidak sama sekali. Di atasnya ada garis dengan semua struktur internalnya, termasuk tabel metode dan indeks blok sinkronisasi. Tetapi kompiler melihat bidang masih dari jenis kelas kami.
Sebagai bukti, saya akan memberikan kliping kecil dari WinDbg:

Di sini Anda dapat melihat beberapa hal yang tidak biasa. Yang pertama adalah bahwa dalam objek alamat pada tabel metode memiliki bidang yang berbeda untuk bidang kelas, seperti yang diharapkan, tetapi alamat nilai bidang adalah satu. Yang kedua - Anda dapat melihat bahwa kedua bidang terletak di offset 4. Saya pikir sebagian besar akan mengerti, tetapi untuk berjaga-jaga, saya akan menjelaskan, langsung di alamat objek ada tautan ke tabel metode. Kolom dimulai dengan offset 4 byte (untuk z 32 bit), dan indeks blok sinkronisasi terletak dengan offset -4.
Sekarang setelah Anda mengetahui apa yang sedang terjadi, Anda dapat mencoba menggunakan offset untuk memanggil sesuatu yang seharusnya tidak Anda panggil.
Untuk melakukan ini, saya mengulangi struktur kelas string di salah satu kelas saya. Benar, saya ulangi hanya permulaan, karena kelas string sangat banyak.
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 Layout berubah sedikit
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClassLikeString SomeInstance; }
Lebih jauh, ketika memanggil FakeLength atau metode CompareTo (), karena offset yang identik dari anggota kelas ini relatif terhadap alamat objek itu sendiri, metode string yang sesuai (dalam kasus ini) akan dipanggil. Butuh waktu cukup lama untuk sampai ke metode pribadi pertama di jalur yang dapat saya gunakan, jadi saya memutuskan metode yang umum. Tapi lapangan itu pribadi, semuanya adil. By the way, metode yang dibuat virtual untuk melindungi terhadap semua jenis optimasi, yang mengganggu pekerjaan (misalnya, embedding), dan juga agar metode ini disebut dengan offset dalam tabel metode.
Jadi, kinerja. Adalah fakta bahwa pesaing langsung dalam tantangan apa yang tidak perlu menyebabkan dan dalam pelanggaran enkapsulasi adalah refleksi. Saya pikir sudah jelas bahwa kita lebih cepat dari hal ini, kita semua tidak menganalisis metadata. Nilai 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 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>(); } }