المبرمجون مثلي ، الذين جاءوا إلى C # مع خبرة واسعة في دلفي ، غالباً ما يفتقرون إلى ما يطلق عليه دلفي المراجع الصفية ، وفي الأوراق النظرية ، metaclasses. عدة مرات في مختلف المنتديات ، صادفت مناقشة جرت على نفس المنوال. يبدأ بسؤال من dolphist سابق حول كيفية إنشاء metaclass في C #. ببساطة ، لا يفهم المتسلطون المشكلة ، ويحاولون توضيح نوع الوحش هذا - metaclass ، dolphists كما يمكنهم أن يفسروا ، لكن التفسيرات قصيرة وغير كاملة ، ونتيجة لذلك فإن المبادرين في حيرة تمامًا بسبب الحاجة إلى كل هذا. بعد كل شيء ، يمكن القيام بنفس الشيء بمساعدة المصانع الانعكاسية والفصلية.
في هذه المقالة ، سأحاول إخبارك ما هي النظارات المعدنية لأولئك الذين لم يسبق لهم مواجهتها. علاوة على ذلك ، اسمح للجميع أن يقرر لنفسه ما إذا كان من الجيد أن يكون هناك شيء من هذا القبيل في اللغة ، أو إذا كان التفكير كافيًا. كل ما أكتبه هنا هو مجرد تخيلات حول كيف كان يمكن أن يكون إذا كان هناك نظارات حقيقية في C #. تتم كتابة جميع الأمثلة الواردة في المقال في هذا الإصدار الافتراضي من C # ، وليس هناك مترجم واحد موجود في الوقت الحالي يمكنه تجميعها.
ما هو metaclass؟
فما هو metaclass؟ هذا نوع خاص يعمل على وصف الأنواع الأخرى. يوجد شيء مشابه جدًا في C # - نوع الكتابة. لكن فقط مماثلة. يمكن أن تصف قيمة type type أي نوع ، ولا يمكن أن تصف metaclass ورثة الفئة المحددة فقط عندما تم إعلان metaclass.
للقيام بذلك ، يكتسب الإصدار الافتراضي الخاص بنا من C # نوع Type <T> ، والذي يعد خليفة Type. لكن النوع <T> مناسب فقط لوصف النوع T أو أحفاده.
سأشرح هذا بمثال:
class A { } class A2 : A { } class B { } static class Program { static void Main() { Type<A> ta; ta = typeof(A);
المثال أعلاه هو الخطوة الأولى لظهور metaclasses. Type Type <T> يسمح لك بتقييد الأنواع التي يمكن وصفها بواسطة القيم المطابقة. قد تكون هذه الميزة مفيدة في حد ذاتها ، ولكن إمكانات النظارات لا تقتصر على هذا.
Metaclasses وأعضاء فئة ثابتة
إذا كان لدى بعض الفئة X أعضاء ثابتة ، فحينها تحصل فئة التعريف <X> على أعضاء متشابهين ، لم يعد ثابتًا ، يمكنك من خلاله الوصول إلى الأعضاء الثابتة في X. دعنا نوضح هذه العبارة المربكة بمثال.
class X { public static void DoSomething() { } } static class Program { static void Main() { Type<X> tx = typeof(X); tx.DoSomething();
هنا ، بشكل عام ، يطرح السؤال - ماذا لو تم الإعلان عن طريقة ثابتة في الفصل X ، يتزامن اسمها ومجموعة المعلمات مع مجموعة الاسم والمعلمة لإحدى أساليب فئة Type ، التي يرثها النوع <X>؟ هناك العديد من الخيارات البسيطة إلى حد ما لحل هذه المشكلة ، لكنني لن أتطرق إليها - للبساطة نعتقد أنه في لغتنا الخيالية المتمثلة في النزاعات لا توجد أسماء سحرية.يجب أن تكون الشفرة أعلاه لأي شخص عادي محيرة - لماذا نحتاج إلى متغير لاستدعاء طريقة ما إذا كنا نستطيع الاتصال بهذه الطريقة مباشرة؟ في الواقع ، في هذا الشكل ، هذه الفرصة غير مجدية. لكن الفائدة تأتي عندما تضيف أساليب الفصل إليها.
طرق الصف
أساليب الفصل هي بنية أخرى لدى Delphi ، لكنها مفقودة في C #. عند التصريح ، يتم تمييز هذه الطرق بفئة الكلمات وهي عبارة عن تقاطع بين الأساليب الثابتة وطرق المثيل. مثل الطرق الثابتة ، فهي غير مرتبطة بمثيل معين ويمكن استدعاؤها من خلال اسم الفئة دون إنشاء مثيل. ولكن ، على عكس الطرق الثابتة ، لديهم معلمة ضمنية هذا. هذا فقط في هذه الحالة ليس مثيلًا للفئة ، ولكنه ملف تعريف ، على سبيل المثال إذا تم وصف طريقة الفصل في الفئة X ، فستكون هذه المعلمة من النوع Type <X>. ويمكنك استخدامه مثل هذا:
class X { public class void Report() { Console.WriteLine($” {this.Name}”); } } class Y : X { } static class Program { static void Main() { X.Report()
هذه الميزة ليست مؤثرة جدا حتى الآن. ولكن بفضل ذلك ، يمكن أن تكون الأساليب الصفية ، على عكس الطرق الثابتة ، افتراضية. بتعبير أدق ، يمكن أيضًا جعل الطرق الثابتة افتراضية ، لكن ليس من الواضح ما يجب فعله بعد ذلك مع هذه الميزة. ولكن مع الأساليب الطبقية ، لا تنشأ مثل هذه المشكلات. النظر في هذا مع مثال.
class X { protected static virtual DoReport() { Console.WriteLine(“!”); } public static Report() { DoReport(); } } class Y : X { protected static override DoReport() { Consloe.WriteLine(“!”); } } static class Program { static void Main() { X.Report()
حسب منطق الأشياء ، عند الاتصال بـ Y.Report ، يجب عرض "Bye!". لكن أسلوب X.Report لا يحتوي على معلومات حول الفئة التي تم استدعاؤها منها ، لذلك لا يمكن الاختيار بشكل حيوي بين X.DoReport و Y.DoReport. نتيجة لذلك ، سيقوم X.Report دائمًا بالاتصال بـ X.DoReport ، حتى لو تم استدعاء التقرير من خلال Y. ليس هناك معنى لجعل طريقة DoReport افتراضية. لذلك ، لا تسمح C # بجعل الطرق الثابتة ظاهرية - سيكون من الممكن جعلها ظاهرية ، لكنك لن تكون قادرًا على الاستفادة من واقعها.
شيء آخر هو أساليب الطبقة. إذا لم يكن التقرير في المثال السابق ثابتًا ، ولكن الفصل ، فسيعلم "" متى تم استدعاؤه من خلال X ، ومتى من خلال Y. وفقًا لذلك ، يمكن للمترجم إنشاء رمز يحدد DoReport المطلوب ، وستؤدي مكالمة إلى Y.Report إلى استنتاج "وداعا!".
هذه الميزة مفيدة في حد ذاتها ، لكنها تصبح أكثر فائدة إذا أضفت إليها القدرة على استدعاء متغيرات الفئة من خلال metaclasses. شيء مثل هذا:
class X { public static virtual Report() { Console.WriteLine(“!”); } } class Y : X { public static override Report() { Consloe.WriteLine(“!”); } } static class Program { static void Main() { Type<X> tx = typeof(X); tx.Report()
ولتحقيق هذا الشكل المتعدد الأشكال بدون نظائر وأساليب صفية افتراضية ، يتعين على كل من صفه من الصف الأول كتابة فئة مساعدة بالطريقة الافتراضية المعتادة. هذا يتطلب بذل جهد أكبر بكثير ، ولن يكون التحكم من قبل المترجم كاملاً ، مما يزيد من احتمال ارتكاب خطأ في مكان ما. وفي الوقت نفسه ، المواقف التي تكون فيها الحاجة إلى تعدد الأشكال على مستوى الكتابة ، وليس على مستوى المثيل ، تصادف بانتظام ، وإذا كانت اللغة تدعم مثل هذا الشكل ، فهذه خاصية مفيدة للغاية.
الصانعين الظاهري
إذا ظهرت metaclasses في اللغة ، فيجب إضافة أدوات إنشاء افتراضية إليها. إذا تم الإعلان عن مُنشئ افتراضي في الفصل ، فيجب أن يتداخل به جميع المتحدرين منه ، أي لديك مُنشئ الخاص بك مع نفس مجموعة المعلمات ، على سبيل المثال:
class A { public virtual A(int x, int y) { ... } } class B : A { public override B(int x, int y) : base(x, y) { } } class C : A { public C(int z) { ... } }
في هذا الرمز ، يجب ألا يتم تصنيف الفئة C ، لأنه لا يحتوي على مُنشئ ذو معلمات int x و int y ، ولكن الفئة B يتم تصنيفها دون أخطاء.
هناك خيار آخر ممكن: إذا لم يتم تراكب المنشئ الظاهري للجد في الوريث ، فإن المترجم يتداخل معه تلقائيًا ، كما هو الحال الآن ، يقوم بإنشاء المنشئ الافتراضي تلقائيًا. كلا الأسلوبين لهما إيجابيات وسلبيات واضحة ، لكن هذا ليس مهمًا للصورة العامة.يمكن استخدام المنشئ الظاهري أينما يمكن استخدام المنشئ العادي. بالإضافة إلى ذلك ، إذا كان لدى الفئة مُنشئ افتراضي ، فإن التعريف الخاص به يحتوي على أسلوب CreateInstance مع نفس مجموعة المعلمات مثل المُنشئ ، وستقوم هذه الطريقة بإنشاء مثيل للفئة ، كما هو موضح في المثال أدناه.
class A { public virtual A(int x, int y) { ... } } class B : A { public override B(int x, int y) : base(x, y) { } } static class Program { static void Main() { Type<A> ta = typeof(A); A a1 = ta.CreateInstance(10, 12);
بمعنى آخر ، سنحصل على فرصة لإنشاء كائنات يتم تحديد نوعها في وقت التشغيل. الآن يمكن القيام بذلك باستخدام Activator.CreateInstance. ولكن هذه الطريقة تعمل من خلال الانعكاس ، لذلك يتم التحقق من صحة مجموعة المعلمات فقط في مرحلة التنفيذ. ولكن إذا كان لدينا نظارات ، فلن يتم تجميع الشفرة ذات المعلمات الخاطئة. بالإضافة إلى ذلك ، عند استخدام الانعكاس ، تترك سرعة العمل الكثير مما هو مرغوب فيه ، وتتيح لك النظارات المعدنية تقليل التكاليف.
استنتاج
لقد فوجئت دائمًا بسبب عدم قيام Halesberg ، وهو المطور الرئيسي لكل من Delphi و C # ، بصنع نظارات في C # ، على الرغم من أنها أثبتت نفسها بشكل جيد في Delphi. ربما تكون النقطة المهمة هي أنه في دلفي (في تلك الإصدارات التي قام بها Halesberg) لا يوجد أي انعكاس تقريبًا ، وليس هناك ببساطة بديل عن النظارات التي لا يمكن قولها عن C #. في الواقع ، ليس من الصعب إعادة عرض جميع الأمثلة من هذه المقالة ، فقط باستخدام تلك الأدوات الموجودة بالفعل في اللغة. ولكن كل هذا سوف يعمل بشكل أبطأ بشكل ملحوظ مما كان عليه مع metaclasses ، وسيتم التحقق من صحة المكالمات في وقت التشغيل ، وليس التجميع. لذلك رأيي الشخصي هو أن C # سوف تستفيد كثيرا إذا ظهرت نظارات في ذلك.