سؤال

كانت طريقة .NET 1.0 لإنشاء مجموعة من الأعداد الصحيحة (على سبيل المثال):

ArrayList list = new ArrayList();
list.Add(i);          /* boxing   */
int j = (int)list[0]; /* unboxing */

عقوبة استخدام هذا هي عدم وجود سلامة وأداء من النوع بسبب الملاكمة والملاكمة.

طريقة .NET 2.0 هي استخدام الأدوية الجيرية:

List<int> list = new List<int>();
list.Add(i);
int j = list[0];

سعر الملاكمة (إلى فهمي) هو الحاجة إلى إنشاء كائن على الكومة ، ونسخ المكدس الصحيح إلى الكائن الجديد والعكس بالعكس من أجل إلغاء اللاثني.

كيف يتغلب استخدام الأدوية الجيلية على هذا؟ هل يبقى عدد صحيح مُحسّن المكدس على المكدس ويتم الإشارة إليه من الكومة (أعتقد أن هذا ليس هو الحال بسبب ما سيحدث عندما يخرج من النطاق)؟ يبدو أنه لا تزال هناك حاجة لنسخه في مكان آخر خارج المكدس.

ما الذي يحدث حقا؟

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

المحلول

عندما يتعلق الأمر بالمجموعات ، تتيح الأدوية الجماهيرية تجنب الملاكمة/التفكيك عن طريق الاستفادة من الفعلي T[] المصفوفات داخليًا. List<T> على سبيل المثال يستخدم أ T[] صفيف لتخزين محتوياتها.

ال مجموعة مصفوفة, بالطبع ، هو نوع مرجعي وبالتالي (في الإصدار الحالي من CLR ، Yada Yada) المخزنة على الكومة. ولكن لأنه أ T[] وليس object[], ، يمكن تخزين عناصر الصفيف "مباشرة": أي أنها لا تزال على الكومة ، لكنها في الكومة في الصفيف بدلاً من أن تكون محاصرًا والحصول على الصفيف ، يحتوي على إشارات إلى الصناديق.

لذلك ل List<int>, ، على سبيل المثال ، ما لديك في المصفوفة "يبدو" مثل هذا:

[ 1 2 3 ]

قارن هذا بـ ArrayList, الذي يستخدم object[] وبالتالي "تبدو" شيء من هذا القبيل:

[ *a *b *c ]

...أين *a, ، إلخ. هي إشارات إلى الكائنات (الأعداد الصحيحة المعبأة):

*a -> 1
*b -> 2
*c -> 3

عذرا تلك الرسوم التوضيحية الخام. آمل أن تعرف ما أعنيه.

نصائح أخرى

إن ارتباكك هو نتيجة لسوء فهم ما هي العلاقة بين المكدس والكومة والمتغيرات. إليك الطريقة الصحيحة للتفكير في الأمر.

  • المتغير هو موقع تخزين يحتوي على نوع.
  • يمكن أن يكون عمر المتغير إما قصيرًا أو طويلًا. من خلال "القصير" نعني "حتى تعود الوظيفة الحالية أو رميات" و "Long" نعني "ربما أطول من ذلك".
  • إذا كان نوع المتغير هو نوع مرجع ، فإن محتويات المتغير هي إشارة إلى موقع تخزين طويل العمر. إذا كان نوع المتغير هو نوع قيمة ، فإن محتويات المتغير هي قيمة.

كتفاصيل تنفيذ ، يمكن تخصيص موقع تخزين مضمون أن يكون قصير الأجل على المكدس. يتم تخصيص موقع التخزين الذي قد يكون طويل الأجل على الكومة. لاحظ أن هذا لا يقول شيئًا عن "أنواع القيمة يتم تخصيصها دائمًا على المكدس". أنواع القيمة ليس تخصيص دائمًا على المكدس:

int[] x = new int[10];
x[1] = 123;

x[1] هو موقع تخزين. انها طويلة العمر. قد يعيش لفترة أطول من هذه الطريقة. لذلك يجب أن يكون على كومة. حقيقة أنه يحتوي على int غير ذي صلة.

أنت تقول بشكل صحيح لماذا يكون الباحث باهظ الثمن:

سعر الملاكمة هو الحاجة إلى إنشاء كائن على الكومة ، ونسخ المكدس عدد صحيح مخصص للكائن الجديد والعكس بالعكس من أجل إلغاء اللاثنين.

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

فلماذا لا يكون متغير عام مكلف؟ إذا كان لديك متغير من النوع T ، ويتم إنشاء T ليكون INT ، فعليك متغير من النوع int ، الفترة. متغير من النوع int هو موقع تخزين ، ويحتوي على int. سواء كان موقع التخزين هذا على المكدس أو الكومة غير ذي صلة تمامًا. ما هو ذا صلة هو أن موقع التخزين يحتوي على int, ، بدلا من احتواء إشارة إلى شيء ما على الكومة. نظرًا لأن موقع التخزين يحتوي على int ، فلا يتعين عليك تحمل تكاليف الملاكمة والملاكمة: تخصيص سعة تخزين جديدة على الكومة ونسخ int إلى التخزين الجديد.

هل هذا واضح الآن؟

يسمح الأدوية بالكتابة الداخلية للقائمة int[] بدلا من بفعالية object[], والتي تتطلب الملاكمة.

إليك ما يحدث بدون الأدوية الجيلية:

  1. أنت أتصل Add(1).
  2. عدد صحيح 1 محاصر في كائن ، والذي يتطلب بناء كائن جديد على الكومة.
  3. يتم تمرير هذا الكائن إلى ArrayList.Add().
  4. الكائن المحاصر محشوة في object[].

هناك ثلاثة مستويات من عدم التوجيه هنا: ArrayList -> object[] -> object -> int.

مع الأدوية:

  1. أنت أتصل Add(1).
  2. يتم تمرير int 1 إلى List<int>.Add().
  3. int محشوة في int[].

لذلك لا يوجد سوى مستويين من عدم التوجيه: List<int> -> int[] -> int.

بعض الاختلافات الأخرى:

  • ستتطلب الطريقة غير الجينية مبلغًا قدره 8 أو 12 بايت (مؤشر واحد ، واحد int) لتخزين القيمة ، 4/8 في تخصيص واحد و 4 في الآخر. وربما يكون هذا أكثر بسبب المحاذاة والحشو. ستتطلب الطريقة العامة فقط 4 بايت من المساحة في الصفيف.
  • تتطلب الطريقة غير الجينية تخصيص int محاصر ؛ الطريقة العامة لا. هذا أسرع ويقلل من GC churn.
  • تتطلب الطريقة غير الجينية قوالب لاستخراج القيم. هذا ليس نوعًا من الأنواع وأبطأ قليلاً.

قائمة ArrayList تعالج النوع فقط object لذلك ، يتطلب استخدام هذا الفصل الصب من وإلى object. في حالة أنواع القيمة ، يتضمن هذا الصب الملاكمة والملاكمة.

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

سعر الملاكمة (إلى فهمي) هو الحاجة إلى إنشاء كائن على الكومة ، ونسخ المكدس الصحيح إلى الكائن الجديد والعكس بالعكس من أجل إلغاء اللاثني.

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

في .NET 1 ، عندما تكون Add الطريقة تسمى:

  1. يتم تخصيص الفضاء على الكومة. تم إجراء مرجع جديد
  2. محتويات i يتم نسخ المتغير في المرجع
  3. يتم وضع نسخة من المرجع في نهاية القائمة

في .NET 2:

  1. نسخة من المتغير i تم تمريره إلى Add طريقة
  2. يتم وضع نسخة من تلك النسخة في نهاية القائمة

نعم i لا يزال يتم نسخ المتغير (بعد كل شيء ، إنه نوع قيمة ، ويتم نسخ أنواع القيمة دائمًا - حتى لو كانت مجرد معلمات طريقة). ولكن لا توجد نسخة زائدة عن الحاجة على الكومة.

لماذا تفكر من حيث WHERE يتم تخزين القيم الكائنات؟ في C# يمكن تخزين أنواع القيمة على المكدس وكذلك الكومة اعتمادًا على ما يختاره CLR.

حيث تحدث الأدوية الفرق WHAT يتم تخزينه في المجموعة. في حالة ArrayList تحتوي المجموعة على إشارات إلى الكائنات المعبأة حيث List<int> يحتوي على قيم int نفسها.

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