سؤال

While working on a C# app I just noticed that in several places static initializers have dependencies on each other like this:

static private List<int> a = new List<int>() { 0 };
static private List<int> b = new List<int>() { a[0] };

دون القيام بأي شيء خاص نجح.هل هذا مجرد الحظ؟هل لدى C# قواعد لحل هذه المشكلة؟

يحرر: (يكرر:بانوس) في ملف معجمي يبدو أن الملك؟ماذا عن عبر الملفات؟

في البحث حاولت التبعية الدورية مثل هذا:

static private List<int> a = new List<int>() { b[0] };
static private List<int> b = new List<int>() { a[0] };

ولم يعمل البرنامج بنفس الطريقة (فشلت مجموعة الاختبار في جميع المجالات ولم أبحث أكثر).

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

المحلول

يبدو أنه يعتمد على تسلسل الخطوط.يعمل هذا الكود:

static private List<int> a = new List<int>() { 1 };
static private List<int> b = new List<int>() { a[0] };

while this code does not work (it throws a NullReferenceException)

static private List<int> a = new List<int>() { b[0] };
static private List<int> b = new List<int>() { 1 };

لذلك، من الواضح أنه لا توجد قواعد للتبعية الدورية.لكن من الغريب أن المترجم لا يشكو ...


تحرير - ماذا يحدث "عبر الملفات"؟فإذا أعلنا هاتين الفئتين:

public class A {
    public static List<int> a = new List<int>() { B.b[0] };
}
public class B {
    public static List<int> b = new List<int>() { A.a[0] };
}

وحاول الوصول إليهم باستخدام هذا الرمز:

try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message.); }
try { Console.WriteLine(A.a); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); }
try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); }

نحن نحصل على هذا الإخراج:

The type initializer for 'A' threw an exception.
Object reference not set to an instance of an object.
The type initializer for 'A' threw an exception.

وبالتالي فإن التهيئة B يسبب استثناء في منشئ ثابت A ومجال الحقوق a مع القيمة الافتراضية (NULL).منذ a يكون null, b لا يمكن أيضًا تهيئتها بشكل صحيح.

If we do not have cyclical dependencies, everything works fine.


يحرر:فقط في حالة أنك لم تقرأ التعليقات، جون سكيت يوفر قراءة مثيرة للاهتمام للغاية: الاختلافات بين المنشئات الثابتة ومهيئات النوع.

نصائح أخرى

يرى القسم 10.4 من مواصفات C# للقواعد هنا:

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

بمعنى آخر، في المثال الخاص بك، تمت تهيئة "b" إلى حالته الافتراضية (خالية) وبالتالي فإن الإشارة إليه في مُهيئ "a" تعتبر قانونية ولكنها قد تؤدي إلى NullReferenceException.

تختلف هذه القواعد عن قواعد Java (انظر القسم 8.3.2.3 من JLS لقواعد Java المتعلقة بالمراجع الأمامية، والتي تكون أكثر تقييدًا).

أنا شخصياً سأتخلص من أدوات التهيئة الثابتة لأنها غير واضحة وأضيف مُنشئًا ثابتًا لتهيئة هذه المتغيرات.

static private List<int> a;
static private List<int> b;

static SomeClass()
{
    a = new List<int>() { 0 };
    b = new List<int>() { a[0] };
}

ثم ليس عليك أن تخمن ما يحدث وستكون نواياك واضحة.

نعم، كنت محظوظا.يبدو أن C# ينفذ التعليمات البرمجية بالترتيب الذي يظهر به في الفصل.

static private List<int> a = new List<int>() { 0 };
static private List<int> b = new List<int>() { a[0] };

سوف تعمل ولكن...

static private List<int> b = new List<int>() { a[0] };
static private List<int> a = new List<int>() { 0 };

سوف تفشل.

أوصي بوضع كل تبعياتك في مكان واحد، فالمنشئ الثابت هو المكان المناسب لذلك.

static MyClass()
{
  a = new List<int>() { 0 };
  b = new List<int>() { a[0] };
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top