هل أنواع القيمة غير قابلة للتغيير بحكم التعريف؟

StackOverflow https://stackoverflow.com/questions/868411

سؤال

كثيرا ما قرأت ذلك structيجب أن تكون s غير قابلة للتغيير - أليست بحكم التعريف؟

هل تعتبر int أن تكون غير قابلة للتغيير؟

int i = 0;
i = i + 123;

يبدو جيدًا - لقد حصلنا على جديد int وإعادته إلى i.ماذا عن هذا؟

i++;

حسنًا، يمكننا التفكير في الأمر كاختصار.

i = i + 1;

ماذا عن struct Point?

Point p = new Point(1, 2);
p.Offset(3, 4);

هل هذا حقا يغير هذه النقطة (1, 2)؟ألا ينبغي لنا أن نفكر في الأمر كاختصار لما يلي مع Point.Offset() إرجاع نقطة جديدة؟

p = p.Offset(3, 4);

خلفية هذا الفكر هي كيف يمكن أن يكون نوع القيمة بدون هوية قابلاً للتغيير؟عليك أن تنظر إليه مرتين على الأقل لتحديد ما إذا كان قد تغير.ولكن كيف يمكنك أن تفعل ذلك دون هوية؟

لا أريد تعقيد التفكير في هذا الأمر من خلال التفكير ref المعلمات والملاكمة.وأنا أيضا على علم بذلك p = p.Offset(3, 4); يعبر عن الثبات أفضل بكثير من p.Offset(3, 4); يفعل.ولكن يبقى السؤال: أليست أنواع القيمة غير قابلة للتغيير بحكم التعريف؟

تحديث

أعتقد أن هناك مفهومين على الأقل - قابلية تغيير المتغير أو الحقل وقابلية تغيير قيمة المتغير.

public class Foo
{
    private Point point;
    private readonly Point readOnlyPoint;

    public Foo()
    {
        this.point = new Point(1, 2);
        this.readOnlyPoint = new Point(1, 2);
    }

    public void Bar()
    {
        this.point = new Point(1, 2);
        this.readOnlyPoint = new Point(1, 2); // Does not compile.

        this.point.Offset(3, 4); // Is now (4, 6).
        this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).
    }
}

في المثال، لدينا حقول - حقل قابل للتغيير وآخر غير قابل للتغيير.نظرًا لأن حقل نوع القيمة يحتوي على القيمة بأكملها، فإن نوع القيمة المخزن في حقل غير قابل للتغيير يجب أن يكون غير قابل للتغيير أيضًا.ما زلت مندهشًا تمامًا من النتيجة - لم أتوقع أن يظل الحقل للقراءة فقط دون تعديل.

المتغيرات (إلى جانب الثوابت) قابلة للتغيير دائمًا، وبالتالي فهي لا تتضمن أي قيود على قابلية تغيير أنواع القيم.


يبدو أن الإجابة ليست بهذه البساطة لذا سأعيد صياغة السؤال.

نظرا لما يلي.

public struct Foo
{
    public void DoStuff(whatEverArgumentsYouLike)
    {
        // Do what ever you like to do.
    }

    // Put in everything you like - fields, constants, methods, properties ...
}

هل يمكن أن تعطي نسخة كاملة من Foo ومثال الاستخدام - قد يشمل ذلك ref المعلمات والملاكمة - بحيث لا يمكن إعادة كتابة كل تكرارات

foo.DoStuff(whatEverArgumentsYouLike);

مع

foo = foo.DoStuff(whatEverArgumentsYouLike);
هل كانت مفيدة؟

المحلول

الكائن غير قابل للتغيير إذا لم تتغير حالته بمجرد إنشاء الكائن.

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


اجابة طويلة:أولًا، مسألة الثبات تنطبق فقط على البنيات أو الفئات ذات الحقول أو الخصائص.الأنواع الأساسية (الأرقام والسلاسل والخالية) غير قابلة للتغيير بطبيعتها لأنه لا يوجد شيء (حقل/خاصية) لتغييرها.5 هو 5 هو 5.أي عملية على الرقم 5 تُرجع فقط قيمة أخرى غير قابلة للتغيير.

يمكنك إنشاء بنيات قابلة للتغيير مثل System.Drawing.Point.كلاهما X و Y لديك أدوات ضبط تقوم بتعديل حقول البنية:

Point p = new Point(0, 0);
p.X = 5;
// we modify the struct through property setter X
// still the same Point instance, but its state has changed
// it's property X is now 5

يبدو أن بعض الناس يخلطون بين الثبات وحقيقة أن أنواع القيمة يتم تمريرها حسب القيمة (ومن هنا اسمها) وليس عن طريق المرجع.

void Main()
{
    Point p1 = new Point(0, 0);
    SetX(p1, 5);
    Console.WriteLine(p1.ToString());
}

void SetX(Point p2, int value)
{
    p2.X = value;
}

في هذه الحالة Console.WriteLine() يكتب "{X=0,Y=0}".هنا p1 لم يتم تعديله بسبب SetX() معدل p2 وهو أ ينسخ ل p1.يحدث هذا بسبب p1 هو نوع القيمة, ، ليس لأنه كذلك غير قابل للتغيير (ليس كذلك).

لماذا يجب أنواع القيمة تكون غير قابلة للتغيير؟أسباب كثيرة...يرى هذا السؤال.يرجع السبب في الغالب إلى أن أنواع القيم القابلة للتغيير تؤدي إلى جميع أنواع الأخطاء غير الواضحة.في المثال أعلاه ربما توقع المبرمج p1 يكون (5, 0) بعد الاتصال SetX().أو تخيل الفرز حسب القيمة التي يمكن أن تتغير لاحقًا.بعد ذلك، لن يتم فرز مجموعتك التي تم فرزها كما هو متوقع.الشيء نفسه ينطبق على القواميس والتجزئة.ال رائع اريك ليبرت (مدونة) وقد كتب أ سلسلة كاملة عن الثبات ولماذا يعتقد أنها مستقبل لغة C#. وهنا أحد الأمثلة له يتيح لك "تعديل" متغير للقراءة فقط.


تحديث:مثالك مع:

this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).

هو بالضبط ما أشار إليه ليبرت في مقالته حول تعديل متغيرات القراءة فقط. Offset(3,4) تم تعديله فعليًا أ Point, ، ولكن كان أ ينسخ ل readOnlyPoint, ، ولم يخصص له شيء قط، فضاع.

و الذي - التي هذا هو السبب في أن أنواع القيمة القابلة للتغيير شريرة:سمحوا لك يفكر أنت تقوم بتعديل شيء ما، بينما تقوم أحيانًا بتعديل نسخة بالفعل، مما يؤدي إلى أخطاء غير متوقعة.لو Point كان غير قابل للتغيير، Offset() سوف تضطر إلى العودة جديدة Point, ، ولم يكن بإمكانك إسنادها إليه readOnlyPoint.ثم تذهب "أوه صحيح، إنه للقراءة فقط لسبب ما.لماذا كنت أحاول تغييره؟من الجيد أن المترجم أوقفني الآن."


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

قلت في أحد التعليقات "لا يمكن أن يكون لديك إشارة إلى قيمة الحقل أو المتغير".أنت تفترض أن كل متغير بنية له نسخة مختلفة، بحيث لا يؤثر تعديل نسخة واحدة على النسخ الأخرى.هذه ليست الحقيقة الكاملة.الخطوط الموضحة أدناه غير قابلة للاستبدال إذا...

interface IFoo { DoStuff(); }
struct Foo : IFoo { /* ... */ }

IFoo otherFoo = new Foo();
IFoo foo = otherFoo;
foo.DoStuff(whatEverArgumentsYouLike); // line #1
foo = foo.DoStuff(whatEverArgumentsYouLike); // line #2

السطران رقم 1 ورقم 2 ليس لهما نفس النتائج...لماذا؟لأن foo و otherFoo الرجوع إلى نفس المثال محاصر من فو.مهما تغير فيه foo في السطر رقم 1 يعكس otherFoo.استبدال السطر رقم 2 foo بقيمة جديدة ولا يفعل شيئًا otherFoo (افترض أن DoStuff() يعود جديد IFoo المثال ولا تعديل foo بحد ذاتها).

Foo foo1 = new Foo(); // creates first instance
Foo foo2 = foo1; // create a copy (2nd instance)
IFoo foo3 = foo2; // no copy here! foo2 and foo3 refer to same instance

تعديل foo1 لن يؤثر foo2 أو foo3.تعديل foo2 سوف تنعكس في foo3, ، ولكن ليس في foo1.تعديل foo3 سوف تنعكس في foo2 ولكن ليس في foo1.

مربك؟التزم بأنواع القيم غير القابلة للتغيير وستتخلص من الرغبة في تعديل أي منها.


تحديث:تم إصلاح الخطأ المطبعي في نموذج التعليمات البرمجية الأول

نصائح أخرى

قابلية التغيير وأنواع القيمة هما شيئان منفصلان.

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

يمكنك كتابة بنيات قابلة للتغيير، ولكن من الأفضل جعل أنواع القيم غير قابلة للتغيير.

على سبيل المثال، يقوم DateTime دائمًا بإنشاء مثيلات جديدة عند القيام بأي عملية.النقطة قابلة للتغيير ويمكن تغييرها.

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

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

أو يمكنك القول بأن كل شيء قابل للتغيير، في أي لغة، لأنه في بعض الأحيان، سيتم استبدال الذاكرة التي تم استخدامها مسبقًا لشيء ما بشيء آخر.

مع ما يكفي من التجريدات، وتجاهل ميزات اللغة الكافية، يمكنك الوصول إلى أي استنتاج تريده.

وهذا يخطئ الهدف.وفقا لمواصفات .NET، فإن أنواع القيم قابلة للتغيير.يمكنك تعديله.

int i = 0;
Console.WriteLine(i); // will print 0, so here, i is 0
++i;
Console.WriteLine(i); // will print 1, so here, i is 1

لكنه لا يزال هو نفسه.المتغير i يتم الإعلان عنها مرة واحدة فقط.وكل ما يحدث لها بعد هذا التصريح فهو تعديل.

في شيء مثل لغة وظيفية ذات متغيرات غير قابلة للتغيير، لن يكون هذا قانونيًا.++i لن يكون ممكنا.بمجرد الإعلان عن المتغير، يصبح له قيمة ثابتة.

في .NET، ليس هذا هو الحال، لا يوجد ما يمنعني من تعديل ملف .NET i بعد أن تم الإعلان عنها.

بعد التفكير في الأمر أكثر قليلاً، إليك مثال آخر قد يكون أفضل:

struct S {
  public S(int i) { this.i = i == 43 ? 0 : i; }
  private int i;
  public void set(int i) { 
    Console.WriteLine("Hello World");
    this.i = i;
  }
}

void Foo {
  var s = new S(42); // Create an instance of S, internally storing the value 42
  s.set(43); // What happens here?
}

في السطر الأخير، وفقًا لمنطقك، يمكننا القول أننا قمنا بالفعل ببناء كائن جديد، واستبدال الكائن القديم بهذه القيمة.لكن هذا غير ممكن!لإنشاء كائن جديد، يجب على المترجم تعيين i متغير إلى 42لكنها خاصة!لا يمكن الوصول إليه إلا من خلال مُنشئ محدد من قبل المستخدم، والذي لا يسمح صراحةً بالقيمة 43 (تعيينها على 0 بدلاً من ذلك)، ثم من خلال set الطريقة التي لها آثار جانبية سيئة.المترجم ليس لديه وسيلة ل فقط إنشاء كائن جديد بالقيم التي يحبها.الطريقة الوحيدة التي s.i يمكن ضبطها على 43 بواسطة تعديل الكائن الحالي عن طريق الاتصال set().لا يمكن للمترجم أن يفعل ذلك، لأنه سيغير سلوك البرنامج (سيتم الطباعة على وحدة التحكم)

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

لا أريد تعقيد التفكير في هذا الأمر من خلال النظر refالمعلمات والملاكمة.أنا أدرك ذلك أيضًا p = p.Offset(3, 4); يعبر عن الثبات بشكل أفضل من p.Offset(3, 4); يفعل.ولكن يبقى السؤال - أليس أنواع القيمة غير قابلة للتغيير بحكم التعريف؟

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

ليست أنواع القيمة غير قابلة للتغيير بحكم التعريف؟

لا ليسوا كذلك:إذا نظرت إلى System.Drawing.Point على سبيل المثال، يحتوي على أداة ضبط بالإضافة إلى أداة getter X ملكية.

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

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

public class foo
{
    public int x;
}

public struct bar
{
    public int x;
}


public class MyClass
{
    public static void Main()
    {
        foo a = new foo();
        bar b = new bar();

        a.x = 1;
        b.x = 1;

        foo a2 = a;
        bar b2 = b;

        a.x = 2;
        b.x = 2;

        Console.WriteLine( "a2.x == {0}", a2.x);
        Console.WriteLine( "b2.x == {0}", b2.x);
    }
}

ينتج عنه:

a2.x == 2
b2.x == 1

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

بالطبع تعد فئة System.String مثالًا رئيسيًا على هذا السلوك.

في العام الماضي ، كتبت منشورًا في المدونة فيما يتعلق بالمشاكل التي يمكنك مواجهتها من خلال عدم جعل الهياكل غير قابلة للتغيير.

يمكن قراءة التدوينة الكاملة هنا

وهذا مثال على كيف يمكن أن تسوء الأمور بشكل فظيع:

//Struct declaration:

struct MyStruct
{
  public int Value = 0;

  public void Update(int i) { Value = i; }
}

نموذج الكود:

MyStruct[] list = new MyStruct[5];

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

for (int i=0;i<5;i++)
  list[i].Update(i+1);

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

مخرجات هذا الكود هي :

0 0 0 0 0
1 2 3 4 5

الآن دعونا نفعل الشيء نفسه، ولكن استبدل المصفوفة بمصفوفة عامة List<>:

List<MyStruct> list = new List<MyStruct>(new MyStruct[5]); 

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

for (int i=0;i<5;i++)
  list[i].Update(i+1);

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

الإخراج هو:

0 0 0 0 0
0 0 0 0 0

التفسير بسيط جدا.لا، إنها ليست ملاكمة/فتح ملاكمة...

عند الوصول إلى عناصر من مصفوفة، سيحصل وقت التشغيل على عناصر المصفوفة مباشرة، لذلك تعمل طريقة Update() على عنصر المصفوفة نفسها.هذا يعني أنه يتم تحديث البنيات نفسها في المصفوفة.

في المثال الثاني، استخدمنا عام List<>.ماذا يحدث عندما نصل إلى عنصر معين؟حسنًا، يتم استدعاء خاصية المفهرس، وهي طريقة.يتم دائمًا نسخ أنواع القيم عند إرجاعها بواسطة إحدى الطرق، وهذا ما يحدث بالضبط:تقوم طريقة مفهرس القائمة باسترداد البنية من مصفوفة داخلية وإعادتها إلى المتصل.نظرًا لأن الأمر يتعلق بنوع قيمة، سيتم إنشاء نسخة، وسيتم استدعاء طريقة Update() على النسخة، وهو ما ليس له بالطبع أي تأثير على العناصر الأصلية للقائمة.

بمعنى آخر، تأكد دائمًا من أن بنياتك غير قابلة للتغيير، لأنك لن تكون متأكدًا أبدًا من موعد عمل النسخة.يكون الأمر واضحًا في أغلب الأحيان، لكن في بعض الحالات يمكن أن يفاجئك حقًا...

لا ليسو كذلك.مثال:

Point p = new Point (3,4);
Point p2 = p;
p.moveTo (5,7);

في هذا المثال moveTo() هو في المكان عملية.يغير البنية التي تختبئ خلف المرجع p.يمكنك أن ترى ذلك من خلال النظر p2:سيكون موقفها قد تغير أيضًا.مع الهياكل غير القابلة للتغيير، moveTo() سيتعين عليك إعادة هيكل جديد:

p = p.moveTo (5,7);

الآن، Point غير قابل للتغيير، وعندما تقوم بإنشاء مرجع له في أي مكان في التعليمات البرمجية الخاصة بك، فلن تحصل على أي مفاجآت.دعنا ننظر إلى i:

int i = 5;
int j = i;
i = 1;

هذا مختلف. i ليست ثابتة، 5 يكون.والمهمة الثانية لا تنسخ مرجعًا إلى البنية التي تحتوي على i لكنه ينسخ المحتوى i.إذن خلف الكواليس يحدث شيء مختلف تمامًا:تحصل على نسخة كاملة من المتغير بدلاً من مجرد نسخة من العنوان الموجود في الذاكرة (المرجع).

سيكون المعادل للكائنات هو مُنشئ النسخ:

Point p = new Point (3,4);
Point p2 = new Point (p);

هنا الهيكل الداخلي لل p يتم نسخه إلى كائن/بنية جديدة و p2 سوف تحتوي على الإشارة إليها.لكن هذه عملية مكلفة جدًا (على عكس مهمة الأعداد الصحيحة أعلاه) ولهذا السبب تميز معظم لغات البرمجة.

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

لا، أنواع القيمة هي لا غير قابل للتغيير حسب التعريف.

أولاً ، كان من الأفضل أن أطرح السؤال "هل تتصرف أنواع القيمة مثل الأنواع غير القابلة للتغيير؟" بدلاً من السؤال عما إذا كانت غير قابلة للتغيير - أفترض أن هذا تسبب في الكثير من الارتباك.

struct MutableStruct
{
    private int state;

    public MutableStruct(int state) { this.state = state; }

    public void ChangeState() { this.state++; }
}

struct ImmutableStruct
{
    private readonly int state;

    public MutableStruct(int state) { this.state = state; }

    public ImmutableStruct ChangeState()
    {
        return new ImmutableStruct(this.state + 1);
    }
}

[يتبع...]

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

على النقيض من ذلك، عندما يتم الإعلان عن موقع تخزين لنوع القيمة، سيقوم النظام بتخصيص مواقع تخزين متداخلة داخل موقع التخزين هذا لكل حقل عام أو خاص يحتفظ به نوع القيمة هذا.يتم الاحتفاظ بكل شيء يتعلق بنوع القيمة في موقع التخزين هذا.إذا قام أحدهم بتعريف متغير foo من النوع Point ومجاليه اثنين X و Y, ، اضغط على 3 و 6 على التوالي.إذا قام أحد بتعريف "المثيل" لـ Point في foo باعتباره زوج من مجالات, ، سيكون هذا المثيل قابلاً للتغيير إذا وفقط foo قابل للتغيير.إذا تم تحديد مثيل لـ Point باعتبارها قيم عقدت في تلك المجالات (على سبيل المثال."3،6")، فإن مثل هذا المثيل بحكم التعريف غير قابل للتغيير، لأن تغيير أحد هذه الحقول قد يؤدي إلى Point لعقد مثيل مختلف.

أعتقد أنه من المفيد التفكير في "مثيل" نوع القيمة على أنه الحقول، وليس القيم التي تحملها.بموجب هذا التعريف، فإن أي نوع قيمة مخزَّن في موقع تخزين قابل للتغيير، والذي توجد له أي قيمة غير افتراضية، سوف يكون كذلك دائماً أن تكون قابلة للتغيير، بغض النظر عن كيفية الإعلان عنها.تصريح MyPoint = new Point(5,8) يبني مثيل جديد من Point, ، مع الحقول X=5 و Y=8, ، ثم يتحور MyPoint وذلك باستبدال القيم الموجودة في حقولها بتلك التي تم إنشاؤها حديثًا Point.حتى لو لم توفر البنية طريقة لتعديل أي من حقولها خارج مُنشئها، فلا توجد طريقة يمكن لنوع البنية من خلالها حماية مثيل من استبدال جميع حقوله بمحتوى مثيل آخر.

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

Threading.Interlocked.Increment(myPoints[0].X);

لو myPoints[0].X يبدأ من صفر وعشرين خيطًا ينفذ الكود أعلاه، سواء في وقت واحد أم لا، myPoints[0].X سوف يساوي عشرين.إذا حاول المرء تقليد الكود أعلاه باستخدام:

myPoints[0] = new Point(myPoints[0].X + 1, myPoints[0].Y);

ثم إذا كان هناك أي موضوع القراءة myPoints[0].X بين الوقت الذي يقرأه فيه موضوع آخر ويعيد كتابة القيمة المنقحة، سيتم فقدان نتائج الزيادة (مع ما يترتب على ذلك من myPoints[0].X يمكن أن ينتهي الأمر بشكل تعسفي بأي قيمة تتراوح بين 1 و20.

تكون الكائنات/الهياكل غير قابلة للتغيير عندما يتم تمريرها إلى دالة بطريقة لا يمكن فيها تغيير البيانات، وتكون البنية التي تم إرجاعها عبارة عن new البنية.المثال الكلاسيكي هو

String s = "abc";

s.toLower();

إذا toLower تتم كتابة الوظيفة بحيث يتم إرجاع سلسلة جديدة تحل محل "s"، فهي غير قابلة للتغيير، ولكن إذا انتقلت الوظيفة حرفًا تلو الآخر لتحل محل الحرف الموجود داخل "s" ولم تعلن أبدًا عن "سلسلة جديدة"، فهي قابلة للتغيير.

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