سؤال

لدي سؤال بخصوص اكتب المنشئين ضمن نوع القيمة.هذا السؤال مستوحى من شيء كتبه جيفري ريختر في CLR عبر C# 3rd ed، حيث يقول (في الصفحة 195 - الفصل 8) أنه لا ينبغي عليك أبدًا تحديد مُنشئ النوع ضمن نوع القيمة حيث توجد أوقات لن يتم فيها استدعاء CLR هو - هي.

لذلك، على سبيل المثال (حسنًا... مثال جيفري ريختر في الواقع)، لا أستطيع أن أفهم، حتى من خلال النظر إلى IL، سبب عدم استدعاء مُنشئ النوع في الكود التالي:

internal struct SomeValType
{
    static SomeValType()
    {
        Console.WriteLine("This never gets displayed");
    }
    public Int32 _x;
}
public sealed class Program
{
    static void Main(string[] args)
    {
        SomeValType[] a = new SomeValType[10];
        a[0]._x = 123;
        Console.WriteLine(a[0]._x);     //Displays 123
    }
}

لذلك، عند تطبيق القواعد التالية لمنشئي النوع، لا أستطيع أن أرى سبب عدم استدعاء مُنشئ نوع القيمة أعلاه على الإطلاق.

  1. يمكنني تحديد مُنشئ نوع القيمة الثابتة لتعيين الحالة الأولية للنوع.
  2. لا يمكن أن يحتوي النوع على أكثر من مُنشئ واحد - لا يوجد مُنشئ افتراضي.
  3. منشئو النوع خاصون ضمنيًا
  4. يتحقق مترجم JIT مما إذا كان مُنشئ النوع قد تم تنفيذه بالفعل في AppDomain هذا.إذا لم يكن الأمر كذلك، فإنه يرسل المكالمة إلى التعليمات البرمجية الأصلية، وإلا فلن يحدث لأنه يعلم أن النوع قد تم "تهيئته" بالفعل.

لذا... لا أستطيع معرفة سبب عدم قدرتي على رؤية مصفوفة النوع هذه قيد الإنشاء.

أفضل تخميني هو أنه يمكن أن يكون:

  1. الطريقة التي يبني بها CLR مصفوفة نوع.كنت أعتقد أنه سيتم استدعاء المُنشئ الثابت عند إنشاء العنصر الأول
  2. لا يقوم الكود الموجود في المنشئ بتهيئة أي حقول ثابتة لذلك يتم تجاهله.لقد جربت تهيئة الحقول الثابتة الخاصة داخل المنشئ ولكن الحقل يظل القيمة الافتراضية 0 - لذلك لا يتم استدعاء المنشئ.
  3. أو... يقوم المترجم بطريقة ما بتحسين استدعاء المنشئ بسبب تعيين Int32 العام - ولكن هذا تخمين غامض في أحسن الأحوال!!

وبغض النظر عن أفضل الممارسات وما إلى ذلك، فأنا مفتون جدًا بها لأنني أريد أن أكون قادرًا على رؤية سبب عدم استدعائها بنفسي.

يحرر:لقد أضفت إجابة لسؤالي أدناه، مجرد اقتباس لما يقوله جيفري ريختر حول هذا الموضوع.

إذا كان لدى أي شخص أي أفكار فسيكون ذلك رائعًا.شكرا جزيلا ، جيمس

هل كانت مفيدة؟

المحلول

ال مواصفات مايكروسوفت C#4 لقد تغير قليلاً عن الإصدارات السابقة ويعكس الآن بشكل أكثر دقة السلوك الذي نراه هنا:

11.3.10 المنشئات الثابتة

تتبع المنشئات الثابتة للهياكل معظم القواعد نفسها بالنسبة للفصول.تنفيذ منشئ ثابت لنوع البنية يتم تشغيله بواسطة أول الأحداث التالية التي تحدث ضمن مجال التطبيق:

  • تتم الإشارة إلى عضو ثابت من نوع البنية.
  • يتم استدعاء المُنشئ المُعلن عنه بوضوح من نوع البنية.

إنشاء القيم الافتراضية (الفقرة 4.3.11) من أنواع الهياكل لا قم بتشغيل المنشئ الثابت.(و مثال على ذلك هو القيمة الأولية من العناصر في صفيف.)

ال مواصفات ECMA و ال مواصفات مايكروسوفت C#3 كلاهما لهما حدث إضافي في تلك القائمة:"تتم الإشارة إلى عضو مثيل من نوع البنية". لذلك يبدو كما لو أن C#3 كان مخالفًا لمواصفاته الخاصة هنا.لقد تم جعل مواصفات C#4 أكثر توافقًا مع السلوك الفعلي لـ C#3 و4.

يحرر...

بعد مزيد من التحقيق، يبدو أن جميع أعضاء المثيلات لديهم حق الوصول تقريبًا يستثني سيؤدي الوصول المباشر إلى الحقل إلى تشغيل المُنشئ الثابت (على الأقل في تطبيقات Microsoft الحالية لـ C#3 و4).

لذا فإن التطبيقات الحالية ترتبط ارتباطًا وثيقًا بالقواعد الواردة في مواصفات ECMA وC#3 مقارنة بتلك الموجودة في مواصفات C#4:يتم تنفيذ قواعد C#3 بشكل صحيح عند الوصول إلى كافة أعضاء المثيل يستثني مجالات؛قواعد C # 4 هي فقط تنفيذها بشكل صحيح للوصول الميداني.

(المواصفات المختلفة كلها متفق عليها - ومن الواضح أنها تم تنفيذها بشكل صحيح - عندما يتعلق الأمر بالقواعد المتعلقة بوصول الأعضاء الثابت والمنشئات المعلنة بوضوح.)

نصائح أخرى

من §18.3.10 من المعيار (انظر أيضًا لغة البرمجة C# كتاب):

يتم تشغيل تنفيذ المُنشئ الثابت للبنية من خلال أول الأحداث التالية التي تحدث داخل مجال التطبيق:

  • عضو مثيل في البنية هو المشار اليه.
  • عضو ثابت في تمت الإشارة إلى الهيكل.
  • منشئ معلن صراحة ل يسمى هيكل.

[ملحوظة:الخلق من القيم الافتراضية (الفقرة 4.3.18) للهيكل الأنواع لا تؤدي إلى تشغيل ثابت منشئ.(مثال على ذلك القيمة الابتدائية للعناصر في صفيف.) ملاحظة النهاية]

لذلك أتفق معك في أن السطرين الأخيرين من برنامجك يجب أن يؤديا إلى تشغيل القاعدة الأولى.

بعد الاختبار، يبدو أن الإجماع هو أنه يتم تشغيل الأساليب والخصائص والأحداث والمفهرسات باستمرار.وهذا يعني أنه صحيح لجميع أعضاء المثيل الصريح يستثني مجالات.لذا، إذا تم اختيار قواعد Microsoft C# 4 للمعيار، فإن ذلك من شأنه أن يجعل تنفيذها يتحول من الصواب في الغالب إلى الخطأ في الغالب.

مجرد وضع هذا كـ "إجابة" حتى أتمكن من مشاركة ما كتبه السيد ريختر نفسه عنه (بالمناسبة، هل لدى أي شخص رابط لأحدث مواصفات CLR، من السهل الحصول على إصدار 2006 ولكن العثور عليه أصعب قليلاً احصل على الأحدث):

بالنسبة لهذا النوع من الأشياء، من الأفضل عادةً النظر إلى مواصفات CLR بدلاً من النظر إلى مواصفات C#.تقول مواصفات CLR:

4.إذا لم يتم وضع علامة BeforeFieldInit، فسيتم تنفيذ طريقة تهيئة هذا النوع على (أي يتم تشغيلها بواسطة):

• الوصول أولاً إلى أي حقل ثابت من هذا النوع، أو

• الاستدعاء الأول لأي طريقة ثابتة من هذا النوع أو

• الاستدعاء الأول لأي مثيل أو طريقة افتراضية من هذا النوع إذا كان نوع قيمة أو

• الاستدعاء الأول لأي منشئ لهذا النوع.

وبما أنه لم يتم استيفاء أي من هذه الشروط، فإن المنشئ الثابت هو كذلك لا استدعى.الأجزاء الصعبة الوحيدة التي يجب ملاحظتها هي أن "_x" هو حقل مثيل وليس حقلًا ثابتًا، وإنشاء مصفوفة من البنيات لا يؤدي إلى ذلك لا استدعاء أي مُنشئ مثيل على عناصر المصفوفة.

عينة أخرى مثيرة للاهتمام:

   struct S
    {
        public int x;
        static S()
        {
            Console.WriteLine("static S()");
        }
        public void f() { }
    }

    static void Main() { new S().f(); }

تحديث: ملاحظتي هي أنه ما لم يتم استخدام الحالة الثابتة، فلن يتم لمس المُنشئ الثابت أبدًا - وهو أمر يبدو أن وقت التشغيل يقرره ولا ينطبق على الأنواع المرجعية.وهذا يطرح السؤال عما إذا كان هذا خطأً متبقيًا لأنه ليس له تأثير يذكر، أو أنه حسب التصميم، أو أنه خطأ معلق.

التحديث 2: شخصيًا، ما لم تكن تفعل شيئًا غير تقليدي في المُنشئ، فإن هذا السلوك من وقت التشغيل لا ينبغي أن يسبب مشكلة أبدًا.بمجرد الوصول إلى الحالة الثابتة، فإنه يتصرف بشكل صحيح.

التحديث3: بالإضافة إلى تعليق LukeH، والإشارة إلى إجابة Matthew Flaschen، فإن تنفيذ واستدعاء المنشئ الخاص بك في البنية يؤدي أيضًا إلى استدعاء المُنشئ الثابت.وهذا يعني أنه في أحد السيناريوهات الثلاثة، يكون السلوك مختلفًا عما هو مكتوب على العلبة.

لقد قمت للتو بإضافة خاصية ثابتة إلى النوع ووصلت إلى تلك الخاصية الثابتة - والتي تسمى المُنشئ الثابت.بدون الوصول إلى الخاصية الثابتة، فقط إنشاء مثيل جديد من النوع، لم يتم استدعاء المُنشئ الثابت.

internal struct SomeValType
    {
        public static int foo = 0;
        public int bar;

        static SomeValType()
        {
            Console.WriteLine("This never gets displayed");
        }
    }

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // Doesn't hit static constructor
            SomeValType v = new SomeValType();
            v.bar = 1;

            // Hits static constructor
            SomeValType.foo = 3;
        }
    }

تحدد ملاحظة في هذا الرابط أن المنشئات الثابتة هي لا يتم استدعاؤه عند الوصول ببساطة إلى المثيلات:

http://www.jaggersoft.com/pubs/StructsVsClasses.htm#default

أعتقد أنك تقوم بإنشاء ARRAY لنوع القيمة الخاص بك.لذلك سيتم استخدام الكلمة الأساسية الجديدة لتهيئة الذاكرة للمصفوفة.

ويصح القول

SomeValType i;
i._x = 5;

مع عدم وجود كلمة رئيسية جديدة في أي مكان، وهو ما تفعله هنا بشكل أساسي.إذا كان SomeValType نوعًا مرجعيًا، فسيتعين عليك تهيئة كل عنصر من عناصر المصفوفة الخاصة بك باستخدامه

array[i] = new SomeRefType();

يعد هذا سلوكًا جنونيًا للسمة "beforefieldinit" في MSIL.إنه يؤثر على C++/CLI أيضًا، لقد قدمت تقريرًا عن الأخطاء حيث أوضحت Microsoft بشكل جيد للغاية سبب كون السلوك على ما هو عليه وأشرت إلى أقسام متعددة في معيار اللغة التي لم توافق/تحتاج إلى تحديث لوصف السلوك الفعلي .لكنها غير قابلة للعرض علنًا.على أي حال، إليك الكلمة الأخيرة من Microsoft (تناقش موقفًا مشابهًا في C++/CLI):

نظرا لأننا نستدعي المعيار هنا ، السطر من القسم الأول ، 8.9.5 يقول هذا:

إذا تم وضع علامة BeforeFieldInit ، فإن ملف يتم تنفيذ طريقة مهيئ النوع في، أو في وقت ما قبل، الوصول الأول إلى أي حقل ثابت محدد لذلك نوع.

هذا القسم في الواقع يذهب إلى التفاصيل حول كيفية تنفيذ اللغة يمكن اختيار منع السلوك أنت تصف.C / CLI يختار لا إلى ، بدلا من ذلك يسمحون للمبرمج للقيام بذلك إذا رغبوا في ذلك.

في الأساس ، نظرا لأن الكود أدناه يحتوي على لا توجد حقول ثابتة على الإطلاق ، JIT صحيح تماما في ببساطة لا استدعاء منشئات الطبقة الثابتة.

نفس السلوك هو ما تراه، على الرغم من أنه بلغة مختلفة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top