Saya tidak menghargai enkapsulasi, atau cara menggunakan tabel metode jenis lain untuk panggilan cepat metode pribadi

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:
MetodeAyubBerartiKesalahanStddevMedian
StructLayoutFieldClr0,0597 ns0,0344 ns0,0396 ns0,0498 ns
ReflectionFieldClr197.1257 ns1.9148 ns1,7911 ns197.4787 ns
StructLayoutMethodClr3,5195 ns0,0382 ns0,0319 ns3,5285 ns
Metode RefleksiClr743.9793 ns13.7378 ns12.8504 ns743.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

Source: https://habr.com/ru/post/id446478/


All Articles