جميل بيلوني

أنواع القيمة (Value types)

ببساطة، تَحوِّي أنواع القيمة (value types) القِيمَة الفِعلِية.

تُشتَقّ جميع أنواع القيمة -والتي تَتضَمَن غالبية اﻷصناف المبنية مُسبَقًا (built-in types)- مِن الصنف System.ValueType. ونظرًا لكَوْن هذا الصنف غيْر قابِل للتوريث بشكل مباشر، تُستخَدَم الكلمة المفتاحية صنف (Struct keyword) لإنشاء نوع قيمة مُخصّص (custom value types) كالمثال بالأسفل.

عند إنشّاء نسخة (instance) جديدة من نوع القيمة، تُستَخَدَم ذاكِرة المَكْدَس (Stack Memory) لتخزينها، والتي تَتسِع بما يتوافق مع حجم النوع المُصرّح عنه. على سبيل المثال، يُخصَّص لكل نوع عَدَدِيّ int مِساحة 32 بت بِـذاكِرة المَكْدَس. ويُلْغَى تَخصّيص هذه المساحة عند خروج النُسخة من النِطاق (scope).

مِن المُهم أن تُدرِك أنه عند إِسْناد متغير من نوع القيمة لآخر، تُنسَخ قيمته لا مَرجِعه للمُتغير الآخر، مما يَعّني أنه قد أصبح لدينا نسختين مُنفَصلتين، لا يؤثر تغيير قيمة احِدَاهُما على قيمة النسخة الآخرى.

struct PersonAsValueType
{
    public string Name;
}
class Program
{
    static void Main()
    {
        PersonAsValueType personA;

        personA.Name = "Bob";

        var personB = personA;

        personA.Name = "Linda";

// ‫يُشير المُتغيّران إلى مواضع مختلفة بالذاكرة
        Console.WriteLine(object.ReferenceEquals(personA, personB));  // 'False' 

        Console.WriteLine(personA.Name); // Outputs 'Linda'
        Console.WriteLine(personB.Name); // Outputs 'Bob'
    }
}

أنواع مرجعية (Reference types)

تتكون الأنواع المَرجعِية (reference types) من قيمة (value) مُخزَّنة بمَكان ما بالذاكرة ومَرجِع (reference) يُشير إلى هذا المكان. يُمكِن القول أنها تَعمَل بصورة مشابهة للمُؤشِرات (pointers) بلغتي C/C++‎.

تُعدّ جميع الأصناف (Classes) -حتى الساكن منها (static class)- من النوع المَرجعِي. يُوضح المثال باﻷسفل استخدام صنف (class) لإنشاء متغير من النوع المَرجِعي وإِسْناده لآخر.

تُخزَّن جميع الأنواع المَرجعِية بقسم الكَوْمَة في الذاكرة (Memory Heap). فعِند إنشاء كائن جديد، تُخصَّص مِساحة من ذاكرة الكَوْمَة له ويُعَاد مَرجِع (reference) يُشير إلى مَوقِع تِلك المساحة. يَتولى كانِس المهملات (garbage collector) مُهِمّة إدارة ذاكرة الكَوْمَة، ولا يُسمَح لك بالتَدخُل أو التحكم بها بنفسك.

بالإضافة إلى مساحة الذاكِرة المُخصَصة للكائن ذاته، تُخصّص مساحة أخرى إضافية لتخزين كلًا من المَرجِع ومعلومات إضافية تحتاجها بيئة التنفيذ المشتركة (CLR) بإطار عمل ‎.NET بصورة مؤقتة.

أحد أهم ما يُفرِّق بين نوعي القيمة والمَرجِع هو أنه عند إِسْناد متغير من نوع مَرجِعي لآخر، يُنسَخ المَرجِع إلى المُتغير الآخر لا القيمة التي يُشير إليها، مما يَعّني أنه قد اَصبح لدينا مَرجِعين يُشيران إلى نفس الكائن. بالتالي، سَيُؤثر أيّ تغيير في القِيم الفِعلية لهذا الكائن على قيمة كلا المَرجِعين.

class PersonAsReferenceType
{
    public string Name;
}
class Program
{
    static void Main()
    {
        PersonAsReferenceType personA;

        personA = new PersonAsReferenceType { Name = "Bob" };

        var personB = personA;

        personA.Name = "Linda";

         // ‫يُشير كلًا من المُتغيّران إلى نفس موضع الذاكرة
        Console.WriteLine(object.ReferenceEquals(personA, personB)); // 'True' 

        Console.WriteLine(personA.Name); // 'Linda'
        Console.WriteLine(personB.Name); // 'Linda'
    }
}

الأنواع المخصصة (Custom Types)

بالإضافة إلى اﻷصناف المبنية مُسبَقًا (built-in types)، يُمكن إنشاء أصناف مُخصّصة (Custom).

الصنف Struct

تَرِث الأصناف (structs) -المُعرَّفة باستخدام الكلمة المفتاحية Struct- النوع System.ValueType تلقائيًا، ولذلك تُعدّ من أنواع القيمة (value types)-كما ذَكرنا مُسبَقًا-، كما تُخزَّن بذاكِرة المَكْدَس (stack).

على سبيل المثال:

Struct MyStruct
{
    public int x;
    public int y;
}

تُمرَّر المُتَغيّرات من نوع القيمة كمُعامِلات للدوال تمريرًا قيميًا (pass by value)، أي تُنسَخ قيمة المُتَغيّر وتُسْنَد لمُعامِل الدالة، وبالتالي إذا غَيرَّت الدالة قيمة المُعامِل فإن هذا التَغيير لا يَنعكِس على قيمة المُتَغيّر الأصلي خارج الدالة؛ لأنه مُجرد نُسخة.

في الشيفرة التالية، تَستقبِل الدالة AddNumbers مُعامِلين x و y من النوع int وهو نوع قيمة. على الرغم من أن الدالة تُزيد قيمة المُعامِل x بمقدار 5، فإن قيمة المُتَغيّر a تظل كما هي؛ لأن المُعامِل x هو، بالواقع، مُجرد نسخة من قيمة المُتَغيّر a وليس المُتَغيّر ذاته.

int a = 5;
int b = 6;

AddNumbers(a,b);

public AddNumbers(int x, int y)
{
    int z = x + y;     // قيمة المتغير‫ z تساوي 11
    x = x + 5;         // ‫غُيرت قيمة x إلى القيمة 10
    z = x + y;         // قيمة المتغير‫ z تساوي 16
}

الصنف class

تَرِث الأصناف (classes) -المُعرَّفة باستخدام الكلمة المفتاحية Class- النوع System.Object تلقائيًا، ولذلك تُعدّ من الأنواع المَرجِعية (reference types)، كما تُخزَّن بقسم الكَوْمَة في الذاكرة (heap).

على سبيل المثال:

public Class MyClass
{
    public int a;
    public int b;
}

تُمرَّر المُتَغيّرات من نوع المَرجِع كمُعامِلات للدوال تمريرًا مَرجعيًا (pass by reference)، أي يُنسَخ مَرجِع المُتَغيّر ويُسند لمُعامِل الدالة. لمّا كانت الدالة تَملِك مُعاملًا يَحمِل مَرجِعًا يُشير إلى الكائن الأصلي ذاته، فإنها إذا غَيرَّت قيمة المُعامِل فإن هذا التَغيير سيَنعكِس على قيمة المُتَغيّر الأصلي خارج الدالة.

في الشيفرة التالية، نَستخدِم نفس المثال السابق لكن ضُمِنِّت المُتَغيّرات من النوع int بداخل صنف class، بحيث تَستقبِل الدالة مُعامِلًا من ذلك الصنف.

الآن، عندما تُغيّر الدالة قيمة المُتَغيّر sample.a، فإن قيمة المُتَغيّر instanceOfMyClass.a ستَتَغيَّر تلقائيًا؛ لأن مُعامِل الدالة، في الواقع، يَحمِل مَرجِعًا يُشير إلى نفس ذات الكائن المُمرَّر لها، وليس مُجرد نسخة منه.

MyClass instanceOfMyClass = new MyClass();
instanceOfMyClass.a = 5;
instanceOfMyClass.b = 6;

AddNumbers(instanceOfMyClass);

public AddNumbers(MyClass sample)
{
    int z = sample.a + sample.b; // قيمة المتغير‫ z تساوي 11
    sample.a = sample.a + 5; // ‫غُيرت قيمة a إلى القيمة 10
    z = sample.a + sample.b; // قيمة المتغير‫ z تساوي 16
}

ترجمة -وبتصرف- للفصلين Custom Types و Stack and Heap من كتاب ‎.NET Framework Notes for Professionals





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن