• 0
AliBazzi

Immutable Types

سؤال

الـ Immutable والتي تعني (عدم قابلية التغير) و Mutable (قابل للتغير) هما مصطلحان تكاد تكون الكتابات عنهما قليلة في كتب البرمجة التطبيقية (التي تعلم لغة بحد ذاتها , ,ولا تعلم مبدائ) , لذلك من المهم فهم هذا المبدأ الهام والذي سيكون له دور هام في المستقبل القريب جدا ...

لو نفذنا البرنامج التالي :

int i = 5;
Console.WriteLine(i + 10);
Console.WriteLine(i);

لو سألنا السؤال التالي : ما هي نتيجة تنفيذ السطر الثالث ؟ هل هي 15 أم 5 ؟ ربما لبديهي أن نعلم بأن الناتج هو 5 ,لماذا ؟ لأن الـ Struct في الـ #C يتم نسخه على الـ RunTime Stack والـ Int في الـ #C هو Struct و بالتالي تنفيذ الأسطر السابقة ينتج لنا Code الـ MSIL التالي :


.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 2
.locals init (
[0] int32 i)
L_0000: nop
L_0001: ldc.i4.5
L_0002: stloc.0
L_0003: ldloc.0
L_0004: ldc.i4.s 10
L_0006: add
L_0007: call void [mscorlib]System.Console::WriteLine(int32)
L_000c: nop
L_000d: ldloc.0
L_000e: call void [mscorlib]System.Console::WriteLine(int32)
L_0013: nop
L_0014: ret
}

لو لاحظنا السطر صاحب الـ Label التالية : L_0003 , لوجدنا أنه يقوم بتنفيذ تعليمة Load للمتغير المحلي 0 (وهو في هذه الحالة i) ويضعه في الـ Runtime Stack

والسطر الذي يليه يقوم بتنفيذ نفس التعليمة لكن للثابت 10 ,والسطر الذي يليهم يقوم بتنفيذ Add لأول قيمتين موجودتين على الـ Stack وهما في هذه الحالة 5,10 ووضع الناتج مرة أخرى في الـ Stack ...وبالتالي فإن قيمة المتحول i محفوظة ولم تتغير و إنما أخذ نسخة عنها ليتم تنفيذ العملية (i+10) وبالتالي نقول عن الـ Int في الـ Net. بأنه Immutable Type , فلنعرف هذا المصطلح .

Immutable Type : هو أي Object يحفظ حالته الداخلية (Internal State) حالما أنشئ , وأي عملية تنفذ عليه ستنتج Object جديد يحمل التغييرات الجديدة وسيبقى الـ Object القديم مصان من التغيير ...

هناك تعريف اّخر أود أن أذكره ورد في كتاب " Framework Design Guidelines " وهو :

Immutable Types are Types that don't have any Public members that can modify this instance

جميل , لكن ما الجديد الذي أضيف لمعلوماتك هنا ؟ ربما لأن ما تراه الاّن هو المنطقي والمتعارف عليه .

(كمعلومة فقط :فإن لغة fortran كانت تقوم بتصرف غريب للغاية عند تنفيذ سطر يحتوي شيء كالتالي : 3+1 , سيقوم Compiler الـ Fortran بزيادة الواحد بمقدار 3 و بالتالي عند ورود رقم واحد مرة أخرى في برنامجك فسيرجع الواحد قيمة 4 !!! أعلم أنا هذا يدعو إلى الجنون , ما يحدث هنا بإختصار أن واحدية الواحد قابلة للتغيير في لغة Fotran وبالتالي فإن الـ Integer Type في Fortran هو Mutable Type أي قابل للتغيير !)

سأصنع Integer Type جديد يكون Mutable في الـ #C وسيدعم مبدأيا الجمع (يستطيع أن يدعم أي عملية معروفة لكن كمثال توضيحي إخترنا الجمع)

class MyMutableInt
{
private int value;
public MyMutableInt(int value)
{
this.value = value;
}
public MyMutableInt Add(int value)
{
return Add(new MyMutableInt(value));
}
public MyMutableInt Add(MyMutableInt myMutableInt)
{
this.value += myMutableInt.value;
return this;
}
public override string ToString()
{
return this.value.ToString();
}
}

وقمنا بإختبار السطور التالية :


MyMutableInt i = new MyMutableInt(5);
Console.WriteLine(i.Add(10));
Console.WriteLine(i);

ربما سنتفق على أن ناتج السطر الثاني هو 15 لكن سنختلف على ناتج السطر الثالث , هل هو 5 أم 15 ؟

15 نعم ,لأننا لو نظرنا لمحتوى الـ Add Method لوجدناها تغير بالقيمة الداخلية (وهذا ما ندعوه بالحالة الداخلية) للـ Object عند الجمع ,وبالتالي يقال عن هذا الـ Class أنه Mutable Type ,ويقال عن الـ Add Method أنها تغير الـ Object أو " The Add Method is Mutating the Object" .

قد يقول أحدكم الحل بسيط ,فلنغير الـ Type السابق إلى Struct عوضا عن Class وبالتالي سينسخ الـ Object إلى الـ Stack وبالتالي سنحصل على Immutable Type , للأسف فإن كلامك خاطئ عزيزي لأن هذا النمط هو بشكل صريح Mutable ولن يفيد إعتقادك بحل المشكلة , وبالعكس فدعوني أقول أن ما نعتقده دوما في أن كل Struct هو Value Type وأن كل Class هو Reference Type ,سيوقعنا في مشاكل كثيرة ...

لنأخذ كمثال الـ String Type هل هو Struct أم Class ؟هو Class لكن لو نفذنا الكود التالي :

string str = "hello";
str.Replace("h", "H");
Console.WriteLine(str);

الـ Replace Method تقوم بإستبدال أي حرف h بحرف H وبالتالي سيكون ناتج السطر الثالث هو : "Hello" وليس "hello" أليس كذلك ؟

خطأ , فناتجه سيكون "hello" , "اّه إنتظر لحظة إكتشفت Bug في الـ String Type ",لا يا عزيزي لم تكتشف شيء ولكن Replace Method لا تقوم بتعديل الـ String الأصلي وإنما تقوم بإعادة String Object جديد يحتوي التغييرات المرجوة , لذا سنقول عن الـ String Type أنه Immutable Type أي لا توجد أي Public Method تقوم بتغيير حالة الـ Object الداخلية , فستقوم أي Method بإعادة String Object جديد يحوي التغييرات الجديدة دون المساس بالـ Object الأصلي , لكن مهلا ! كيف تم ذلك والـ String هو Class أي Reference Type ؟

وهذا ما أردت إيضاحه , فقاعدة أن كل Struct هو Immutable Type وأنا كل Class هو Mutable Type غير صحيحة ! والـ String أكبر مثال .

ليس القصد من طرح هذا الموضوع هو تشتيت معلوماتكم , وإنما تنبيهكم إلى هذا الموضوع الهام .

فللناقش أهم الفوائد المرجوة من تصميم إي Type كـ Immutable :

1- أبسط سبب هو محاكاة ما نعرفه و نتقنه طوال حياتنا , فالـ Int هو Immutable والكل ينتظر نتائج صحيحة من برامجنا , وبالطبع لا ننتظر مزحات Fotran السميكة ,والكل أيضا ينتظر سلوك الـ String هذا لأنه لو نفذنا البرنامج التالي :


string str1 = "Hello";
string str2 = "Man";
Console.WriteLine(str1.Replace("Hello","Bye") + str2);
Console.WriteLine(str1);
Console.WriteLine(str2);

فنتوفع من تنفيذ السطر الرابع أن يكون الناتج هو : "Hello" حتى بعد تنفيذ السطر الثالث وما يحتويه من أشياء تخدع ,(لمن لم يقتنع بفائدة الـ String وكونه Immutable فكر للحظات لو كان mutable ... ما النتيجة المتوقعة من برنامج كالسابق ؟ و كيف سأعيد كلمة "hello" إلى برنامجي ؟)

2-بكلمات موجزة الـ Immutable Types تعالج الضعف الموجود في الـ Van Neuman Model للكمبيوترات الحالية فالـ Threading المعروف حاليا يعتمد على مشاركة المصادر (Shared Resources) في عمليات المزامنة ,وبالتالي فإن كل عملنا في برامج الـ Multi-Threading يتمحور حول حل مشكلة المصادر المشتركة و المزامنة الصحيحة فيما بينها (لأن ما نقوم بقرائته أو الكتابة عليه هو Mutable Object وبالتالي الخوف من القراءة و الكتابة في "نفس الوقت")

موضوع الـ Immutability و إعتمادها كحل أساسي لمشكلة الـ Parallel Programming موضوع كبير و يتعدى نطاق هذا المقال ...

تعلم تصميم Immutable Types هو أمر كبير و يتعلق بشكل أساسي بقدرة اللغة التي تعمل عليها ,لذلك سأناقش الـ #C كلغة , و سأصمم Complex Number Type وسيكون Immutable :

class Complex
{
private double real;
private double imaginary;
public Complex(double Real, double Imaginary)
{
this.real = Real;
this.imaginary = Imaginary;
}
public Complex Add(Complex other)
{
//(a + bi) + (c + di): = (a + c) + (b + d)i
return new Complex(this.real + other.real, this.imaginary + other.imaginary);
}
public static Complex operator +(Complex left, Complex right)
{
return left.Add(right);
}
public Complex Multiplication(Complex other)
{
//(a + bi)(c + di): = (ac − bd) + (bc + ad)i
return new Complex(this.real * other.real - this.imaginary * other.imaginary,
this.imaginary * other.real + this.real * other.imaginary);
}
public static Complex operator *(Complex left, Complex right)
{
return left.Multiplication(right);
}
public override string ToString()
{
return this.real + "+" + this.imaginary + "i";
}
}

وكود الإختبار :

Complex first = new Complex(10, 4);
Console.WriteLine(first);
Complex seceond = new Complex(2, 3);
Console.WriteLine(seceond);
Console.WriteLine(first + seceond);
Console.WriteLine(first * seceond);
Console.WriteLine(first + seceond + new Complex(2, 0));

كما ذكرت سابقا , الـ Complex Type هو Immutable مع إنه Reference Type فتصميم Type على أنه Immutable لا يتعلق بنوع الـ Type بقدر ما يتعلق بطريقة تصميم الـ Type , فلو لاحظنا Add Method ,Multiplication Method لوجدنا أنهما تعيدان Object جديد , ولا تعدل أي من البارمترين Left,Right بل فقط تستعملهم للقراءة ... والـ Complex Type المصمم سابقا حقق شرط هام من شروط الـ Immutability وهو حفاظ الـ Object على بنيته الداخلية ويصونها من التغيير في كل فترة حياته .

بقي فكرة أخيرة لكنها مهمة جدا , فلنأخذ المثال التالي :

class MyImmutable
{
private int[] array = new int[] {0,1,2,3,4,5,6,7,8,9};
public int[] Numbers
{
get { return this.array; }
}
}

هذا الـ Type صمم ليكون Immutable والدليل على ذلك أن الـ Array التي تحوي الأعداد الأساسية , والتي لا يمكن الوصول لها إلا عن طريق الـ Numbers Property والتي تحوي على Get Accessor أي إنها Read-only ممتاز , إذا لن يستطيع أحد تنفيذ الكود التالي :

MyImmutable obj = new MyImmutable();
obj.Numbers[0] = -1;

أليس كذلك , سيعلمنا الـ Compiler بوجود خطأ عند محاولة تنفيذ السطر الثاني ... للأسف سيتم تنفيذ البرنامج السابق دون أي مشاكل , ما هذا الكلام ,وما دور الـ Get ?

الـ Get Accessor هنا يحمي تنفيذ سطر كالتالي :

obj.Numbers = new int[]{1,2};

لكن الـ Array بحد ذاتها Mutable وليست Immutable !!!

وبالتالي يجب أخذ الإنتباه إلى أن الـ Properties لا تفيد إذا لم يكن ما تتم حمايته Immutable !

كحل للمسألة السابقة يوجد Class إسمه System.Collections.ObjectModel.ReadOnlyCollection<T> هذا الـ Type يقوم بحماية أي Collection ويجعلها Read only وتحديدا يقبل أي Collection تحقق IEnumerable<T>

لذا سيكون الحل للمشكلة السابقة بالكود التالي :

class MyImmutable
{
private int[] array = new int[] {0,1,2,3,4,5,6,7,8,9};
public System.Collections.ObjectModel.ReadOnlyCollection<int> Numbers
{
get { return new System.Collections.ObjectModel.ReadOnlyCollection<int>(this.array);
}
}
}

أخيرا , وكمثال أخير (أعدكم ;) ) ماذا لو كانت محتويات الـ Array السابقة لمعلومات ليست Immutable كمثال :لو إحتوت الـArray السابقة على Objects من نمط MyInt وليكن كالتالي وهو Mutable :

class MyInt
{
public int Value;
public MyInt(int value)
{
this.Value = value;
}
public override string ToString()
{
return this.Value.ToString();
}
}

فسيكون المثال السابق كالتالي :

class MyImmutable
{
private MyInt[] array = new MyInt[] {new MyInt(5),new MyInt(9)};
public System.Collections.ObjectModel.ReadOnlyCollection<MyInt> Numbers
{
get { return new System.Collections.ObjectModel.ReadOnlyCollection<MyInt>(this.array); }
}
}

هل الـ Array محمية ؟ حتى بعد تطبيق الـ ReadOnlyCollection ? الجواب : لا , لأن ما تحميه الـ ReadOnlyCollection هو بحد ذاته Mutable !

فالكود التالي يعمل بسهولة :

MyImmutable obj = new MyImmutable();
foreach (MyInt number in obj.Numbers)
number.Value = 0;

وبالتالي عند تصميم أي نمط كـ Immutable يجب الإنتباه إلى أن الـ Object Graph بحد ذاتها لا تحوي أنماط Mutable وإلا سيتم خرق الـ Immutability بسهولة !

أتمنى أن أكون قد أضأت على موضوع معقد و مهم كهذا الموضوع , بإنتظار أسألتكم و مشاركاتكم

___________________________________________________________________________________________________________________

مراجع :

http://codebetter.com/blogs/patricksmacchia/archive/2008/01/13/immutable-types-understand-them-and-use-them.aspx

http://blogs.msdn.com/ericlippert/archive/2007/11/13/immutability-in-c-part-one-kinds-of-immutability.aspx

http://weblogs.asp.net/bleroy/archive/2008/01/16/immutability-in-c.aspx

1

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

7 إجابة على هذا السؤال .

  • 0

من الجيد وجود هذا النوع من المقالات الذى يتطرق للاساسيات و ال Language Internals التى يتجاهلها الكثيرين

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

جزاك الله الخير,

لدي سؤال, استاذنا علمنا انه Primitive Type لا يمكن القول انها mutable أو Immutable. هذه المصطلحات فقط للصفوف؟

شكرا,,

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

Really nice article :)

I touch this a lot in my day to day job, we've a simple rule when we design object models here, we set all our data structures to be immutable unless they need to be otherwise, it's better to design them this way, because this gives you an extra layer of protection, especially in multithreaded environment

Nice work :)

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

لدي سؤال أخر. هل تعلم كتاب C# يعلم هذه المفاهيم؟ وليس كتاب تعليم C# كلغة برمجة.

وشكرا،،

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

There's a book that teaches all this, and it's very good and using C# in examples, it's called:

Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

شكرا جزيلا لك اخ Mohamed Meshref

شكرا,,

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

There's a book that teaches all this, and it's very good and using C# in examples, it's called:

Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries

قرأت هذا الكتاب الصيفية الماضية , ة أقرأه حالية مرة ثانية :) فعلا كتاب رائع , و هذا الكتاب هو المرجع لأي مبرمج داخل مايكروسفت يقوم بالبرمجة لـ .Net ...

جزاك الله الخير,

لدي سؤال, استاذنا علمنا انه Primitive Type لا يمكن القول انها mutable أو Immutable. هذه المصطلحات فقط للصفوف؟

شكرا,,

أولا كل الـ Primitive Types هي Immutable ... ولا فرق بين Class و Struct ,كما تعلم الـ String هو Primitive type وهو Class ...

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

من فضلك سجل دخول لتتمكن من التعليق

ستتمكن من اضافه تعليقات بعد التسجيل



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

  • يستعرض القسم حالياً   0 members

    لا يوجد أعضاء مسجلين يشاهدون هذه الصفحة .