مرحبا. أود أن أوضح لك مثالًا على استخدام
StructLayout لشيء أكثر إثارة من الأمثلة مع البايتات والإحداثيات وأنواع بدائية أخرى ، عندما يحدث كل شيء بشكل واضح.
قبل الشروع في انتهاك سريع للتغليف ، يجدر التذكير بإيجاز ما هو StructLayout. بالمعنى الدقيق للكلمة ، إنها حتى
StructLayoutAttribute ، وهي سمة تسمح لك بإنشاء هياكل وفصول مشابهة للاتحاد في C ++. تسمح لك هذه السمة بالتحكم في موضع أعضاء الفصل في الذاكرة (باستخدام الإزاحة). وفقا لذلك ، يتم وضعها فوق الصف.
عادةً ، إذا كان للفصل حقلان ، نتوقع ترتيبهما بالتتابع ، أي أنهما سيكونان مستقلين عن بعضهما البعض (لا تتداخل). ومع ذلك ، يسمح لك
StructLayout بتحديد أن موقع الحقول لن يتم تعيينه بواسطة البيئة ، ولكن بواسطة المستخدم. لتحديد إزاحة الحقول بشكل صريح ، يجب علينا استخدام المعلمة
LayoutKind.Explicit .
للإشارة إلى الإزاحة من بداية الفئة / البنية (المشار إليها فيما يلي "الفئة" فقط) التي نريد وضع الحقل فيها ، نحتاج إلى وضع سمة
FieldOffset عليها. تأخذ هذه السمة كمعلمة عدد البايتات - الإزاحة من بداية الفصل. من المستحيل تمرير قيمة سالبة ، حتى لا تفسد المؤشرات إلى جدول الطريقة أو فهرس كتلة المزامنة. لذلك سيكون أكثر تعقيدًا قليلاً.
لنبدأ كتابة الكود. بادئ ذي بدء ، أقترح البدء بمثال بسيط. إنشاء الصف التالي:
public class CustomClass { public override string ToString() { return "CUSTOM"; } public virtual object Field { get; } = new object(); }
بعد ذلك ، نستخدم الآلية الموضحة أعلاه لتحديد إزاحات الحقل بشكل صريح.
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClass SomeInstance; }
في الوقت الحالي ، سأؤجل التفسيرات وأستخدم الفصل المكتوب كما يلي:
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(); } }
استدعاء الأسلوب
GetType () بإرجاع سلسلة ، الأسلوب
ToString () غير مطيع ويعطينا السلسلة "4564".
تفريغ الدماغ: ما الذي سيتم عرضه بعد استدعاء الخاصية الافتراضية
CustomClass ؟
كما خمنت بالفعل ، قمنا بتهيئة
CustomStructWithLayout ، كلا الوصلات فارغان ، ثم نقوم بتهيئة الحقل من
نوعنا ، ثم نقوم بتعيين السلسلة إلى حقل Str. نتيجة لذلك ، لا يشير
ارتباط CustomClass إلى كائن
CustomClass ، ولكنه يشير إلى كائن System.string (بما في ذلك جدول الطرق وفهرس وحدة المزامنة). لكن المترجم يرى أن الحقل لا يزال من نوع فئتنا.
لإثبات ، هنا لقطة صغيرة من WinDbg:

هنا يمكنك رؤية بعض الأشياء غير العادية.
- في حقول الكائنات CustomStructWithLayout لها عناوين مختلفة من methodtables (متوقع للغاية) ، ولكن عناوين الكائنات هي نفسها.
- والثاني هو أنه يمكنك أن ترى أن كلا الحقلين يقعان في الإزاحة 4. أعتقد أن معظمهم سوف يفهمون ، لكن فقط في الحالة ، سأشرح ، مباشرة ، إلى عنوان الكائن الذي وضع رابطًا لجدول الأساليب. تبدأ الحقول بإزاحة مكونة من 4 بايت (32 بت) ، ويقع فهرس كتلة المزامنة مع إزاحة -4. وبالتالي ، كلا الكائنات في نفس الإزاحة.
الآن بعد أن اكتشفت ما يحدث ، يمكنك محاولة استخدام الإزاحة للاتصال بما لم يكن من المفترض أن يسمى.
لهذا ، كررت بنية فئة السلسلة في أحد صفي. لكنني كررت البداية فقط ، لأن سلسلة الفصل ضخمة جدًا وأنا كسول جدًا.
ملاحظة: لا تكون الإحصائيات والإحصائيات ثابتة ، فقط للمتعة.
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; } }
حسنا ، سيتم تغيير الهيكل مع التخطيط قليلا.
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClassLikeString SomeInstance; }
علاوة على ذلك ، عند استدعاء
FakeLength أو الأسلوب
CompareTo () ، بسبب الإزاحة المتطابقة لأعضاء الفئة هؤلاء بالنسبة إلى عنوان الكائن نفسه ، سيتم استدعاء طريقة السلسلة المقابلة (في هذه الحالة).
الوصول إلى أول طريقة خاصة من السلسلة التي يمكنني استخدامها كانت طويلة جدًا ، لذا توقفت عند واحدة عامة. لكن الحقل خاص ، كل شيء صادق. بالمناسبة ، يتم جعل الطرق افتراضية للحماية من أي تحسينات تتداخل مع العمل (على سبيل المثال ، التضمين) ، وكذلك بحيث يتم استدعاء الأسلوب بواسطة الإزاحة في جدول الطريقة.
لذلك ، الأداء. من الواضح أن المنافس المباشر في استدعاء الأشياء التي لا يجب استدعاءها وانتهاك التغليف يكون انعكاسًا. أعتقد أنه من الواضح أننا أسرع من هذا الشيء ، لأننا لا نحلل البيانات الوصفية. القيم الصحيحة:
طريقة | وظيفة | متوسط | خطأ | StdDev | متوسط |
---|
StructLayoutField | CLR | 0.0597 ن | 0.0344 ن | 0.0396 ن | 0.0498 ن |
ReflectionField | CLR | 197.1257 ن | 1.9148 ن | 1.7911 ن | 197.4787 ن |
StructLayoutMethod | CLR | 3.5195 ن | 0.0382 ن | 0.0319 ن | 3.5285 ن |
ReflectionMethod | CLR | 743.9793 ن | 13.7378 ن | 12.8504 ن | 743.8471 ن |
إليك جزءًا طويلًا من التعليمات البرمجية مع كيفية قياس الأداء (إذا احتاج شخص ما إلى ذلك):
قانون [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>(); } }
النسخة الروسية