C # 8 وصلاحية لاغية. كيف نعيش مع هذا

مرحبا زملائي! حان الوقت للإشارة إلى أن لدينا خططًا لإصدار كتاب Ian Griffiths الأساسي على C # 8:


في هذه الأثناء، على موقعه بلوق، و نشرت مؤلف اثنين ذات الصلة المواد التي يعتبرها تعقيدات ظواهر جديدة مثل nullability، خالية نسيان، وخالية الوعي. لقد قمنا بترجمة المادتين تحت عنوان واحد ونقترح مناقشتها.

تسمى الميزة الجديدة الأكثر طموحًا في C # 8.0 بمراجع لاغية .

الغرض من هذه الميزة الجديدة هو تخفيف الأضرار الناجمة عن شيء خطير ، والذي وصفه عالم الكمبيوتر توني هوار ذات مرة بأنه " خطأ مليار دولار ". يحتوي C # على كلمة أساسيةnull(ما يعادله موجود في العديد من اللغات الأخرى) ، ويمكن إرجاع جذور هذه الكلمة الرئيسية إلى اللغة Algol W ، التي شارك في تطويرها Hoar. في هذه اللغة القديمة (ظهرت في عام 1966) ، يمكن أن تتلقى المتغيرات التي تشير إلى أمثلة من نوع معين معنى خاصًا يشير إلى أنه لم تتم الإشارة إلى هذا المتغير في أي مكان في الوقت الحالي. تم اقتراض هذه الفرصة على نطاق واسع ، واليوم يعتقد العديد من الخبراء (بما في ذلك Hoar نفسه) أنها أصبحت أكبر مصدر لأخطاء البرامج المكلفة في كل العصور.

ما هو الخطأ في افتراض صفر؟ في عالم قد يشير فيه أي رابط إلى الصفر ، يجب عليك مراعاة ذلك في أي مكان يتم فيه استخدام أي روابط في التعليمات البرمجية الخاصة بك ، وإلا فإنك تخاطر برفضك في وقت التشغيل. في بعض الأحيان لا يكون الأمر مرهقًا ؛ إذا قمت بتهيئة متغير بتعبير newفي نفس المكان الذي أعلنت فيه ، فأنت تعلم أن هذا المتغير لا يساوي الصفر. لكن حتى مثل هذا المثال البسيط محفوف ببعض الحمل المعرفي: قبل إصدار C # 8 ، لم يكن المترجم يخبرك إذا كنت تفعل أي شيء يمكن أن يحول هذه القيمة إليه null. ولكن بمجرد أن تبدأ في خياطة أجزاء مختلفة من التعليمات البرمجية ، يصبح من الصعب جدًا الحكم بشكل مؤكد على مثل هذه الأشياء: ما مدى احتمالية عودة هذه الخاصية التي أقرأها الآن null؟ هل يجوز الإرسالnullفي هذه الطريقة؟ في أي حالات يمكنني التأكد من أن الطريقة التي أتصل بها لن تضبط هذه الحجة outعلى nullقيمة مختلفة؟ علاوة على ذلك ، لا يقتصر الأمر على التذكر للتحقق من هذه الأشياء ؛ ليس من الواضح تمامًا ما يجب عليك فعله إذا وصلت إلى الصفر.

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

أما بالنسبة لأنواع المراجع ، قبل C # 8.0 ، لم يتم تعيين جواز صفر بشكل افتراضي فقط ، ولكن لا يمكن تعطيله أيضًا.

في الواقع ، ولأسباب التوافق مع الإصدارات السابقة ، تستمر صلاحية الصفر في العمل بشكل افتراضي حتى في C # 8.0 ، حيث تظل وظائف اللغة الجديدة في هذا المجال معطلة حتى تطلبها صراحة.

ومع ذلك ، بمجرد تمكين هذه الميزة الجديدة - كل شيء يتغير. أسهل طريقة لتنشيطه هي إضافته <Nullablegt;enable</Nullablegt;داخل العنصر.<PropertyGroup>في ملفك .csproj. (ألاحظ أن المزيد من التحكم في الصغر متوفر أيضًا. إذا كنت حقًا بحاجة إليها حقًا ، يمكنك تكوين السلوك بحيث يتم السماح به nullبشكل منفصل في كل سطر. ولكن ، عندما قررنا مؤخرًا تضمين هذه الميزة في جميع مشاريعنا ، اتضح أنه سيتم تنشيطها على نطاق مشروع واحد في كل مرة هو مهمة قابلة للتنفيذ.)

عندما يتم nullتنشيط الروابط المسموح بها في C # 8.0 بالكامل ، يتغير الموقف: الآن ، افتراضيًا ، من المفترض أن الروابط لا تسمح بالصفر فقط إذا لم تحدد أنت العكس تمامًا ، تمامًا كما هو الحال مع الأنواع المهمة ( حتى بناء الجملة هو نفسه: يمكنك كتابة int؟ ، إذا كنت تريد حقًا أن تكون القيمة الصحيحة اختيارية. الآن تكتب سلسلة؟ ، إذا كنت تقصد أنك تريد إما رابطًا إلى سلسلة أوnull.)

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

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

تعمل C # 8.0 على تسهيل عملية إدخال الروابط المتساهلة null، لأنها تتيح لك تقديم هذه الميزة تدريجيًا. لا يحتاج المرء إلى الاختيار بنعم أو لا. هذا يختلف تمامًا عن الميزة async/awaitالمضافة في C # 5.0 ، والتي تميل إلى الانتشار: في الواقع ، العمليات غير المتزامنة تفرض على المتصل أن يكونasyncوبالتالي ، يجب أن يكون الرمز الذي يستدعي هذا المتصل async، وما إلى ذلك ، في أعلى المكدس. لحسن الحظ ، nullيتم إنشاء الأنواع التي تسمح بشكل مختلف: يمكن تنفيذها بشكل انتقائي وتدريجي. يمكنك العمل من خلال الملفات واحدة تلو الأخرى ، أو حتى سطرا بسطر ، إذا لزم الأمر.

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

لذا ، إذا كنت تفكر في إدخال أنواع تسمح بذلكnull، تأكد من ملاحظة أنه يمكنك تقديم هذه الميزة خطوة بخطوة.

التحذيرات فقط

إن أقسى مستوى من التحكم في المشروع بأكمله بعد تشغيل / إيقاف بسيط هو القدرة على تنشيط التحذيرات بغض النظر عن التعليقات التوضيحية. على سبيل المثال ، إذا قمت بتمكين الافتراض الصفري تمامًا لـ Corvus.ContentHandling.Json في مستودع Corvus.ContentHandling ، مضيفًا <Nullablegt;enable</Nullablegt;إلى مجموعة الخصائص في ملف المشروع ، فستظهر على الفور حالتها الحالية 20 تحذيرًا من المترجم. ومع ذلك ، إذا استخدمته بدلاً من ذلك ، <Nullablegt;warnings</Nullablegt;فسوف أحصل على تحذير واحد فقط.

لكن انتظر! لماذا ستظهر لي تحذيرات أقل؟ في النهاية ، لقد طلبت هنا تحذيرات. الجواب على هذا السؤال ليس واضحًا تمامًا: الحقيقة هي أن بعض المتغيرات والتعبيرات يمكن أن تكون nullغافضة تمامًا.

Null Neutrality

C # يدعم تفسيرين للصلاحية الصفرية. أولاً ، يمكن الإعلان عن أي متغير من النوع المرجعي على أنه قبول أو عدم قبول null، وثانيًا ، سيستنتج المترجم nullكلما أمكن ذلك منطقيًا ما إذا كان هذا المتغير يمكن أن يكون في أي نقطة معينة في الرمز أم لا. تتناول هذه المقالة فقط المجموعة الأولى من المقبوليةnull، أي عن النوع الثابت للمتغير (في الواقع ، هذا لا ينطبق فقط على المتغيرات والمعلمات والحقول القريبة منها روحًا ؛ يتم nullتحديد كل من المقبولية الثابتة والخصم المنطقي لكل تعبير في C #.) في الواقع ، المقبولية nullبمعناها الأول ، الذي نفكر فيه هو امتداد لنظام النوع.

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

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

ربما لست مضطرًا لتوضيح معنى "الحياد" في هذه الحالة ، نظرًا لأنه من هذا المنطلق كان C # يعمل دائمًا ، لذا فأنت تفهم كل شيء ... على الرغم من أن هذا ربما يكون غير شريف قليلاً. لذا استمع: في عالم يُعرف فيه بالمقبولية null، فإن أهم خاصية nullللتعابير المحايدة هي أنها لا تسبب تحذيرات بشأن القبول الباطل. يمكنك تعيين تعبير خالٍ من الحياد nullكمتغير مسموح به ، ولكن غير مسموح به. Null- المتغيرات المحايدة (بالإضافة إلى الخصائص والحقول وما إلى ذلك) ، يمكنك تعيين التعبيرات التي يعتبرها المحول البرمجي "ممكن null" أو "لا null".

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

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

public static class NullableAwareClass
	{
	    public static string? GetNullable() => DateTime.Now.Hour > 12 ? null : "morning";
	

	    public static int RequireNonNull(string s) => s.Length;
	}

علاوة على ذلك ، في مشروع آخر ، يمكنني كتابة هذا الرمز في السياق الذي يتم فيه تنشيط تحذيرات الصلاحية الفارغة ، ولكن التعليقات التوضيحية المقابلة معطلة:

static int UseString(string x) => NullableAwareClass.RequireNonNull(x);

نظرًا لتعطيل التعليقات التوضيحية حول الصلاحية الخالية ، فإن المعلمة xهنا nullمحايدة. هذا يعني أن المترجم لا يمكنه تحديد ما إذا كان هذا الرمز صحيحًا أم لا. إذا أصدر المترجم تحذيرات في الحالات التي تكون فيها nullالتعبيرات المحايدة مختلطة مع تلك التي تأخذ في الاعتبار null، يمكن اعتبار نسبة كبيرة من هذه التحذيرات مشكوكًا فيها - وبالتالي ، لا يتم إصدار التحذيرات.

مع هذا الغلاف ، أخفيت حقيقة أن الشفرة تأخذ في الاعتبار الصلاحية null. هذا يعني أنه يمكنني الآن الكتابة هكذا:

	int x = UseString(NullableAwareClass.GetNullable());

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

int y = NullableAwareClass.RequireNonNull(NullableAwareClass.GetNullable());

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

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

int z = NullableAwareClass.GetNullable().Length;

إذا كانت شفرتك جيدة التصميم ، فلا يجب أن يكون هناك عدد كبير من الأخطاء من هذا النوع.

شرح تدريجي للمشروع بأكمله

بعد اتخاذ الخطوة الأولى - فقط قم بتنشيط التحذيرات ، ثم يمكنك المتابعة إلى التنشيط التدريجي للتعليقات التوضيحية ، ملفًا تلو الآخر. من الملائم إدراجها على الفور في المشروع بأكمله ، ومعرفة أي تحذيرات للملفات تظهر - ثم تحديد ملف فيه تحذيرات قليلة نسبيًا. مرة أخرى ، قم بإيقاف تشغيلها على مستوى المشروع بأكمله ، واكتب في أعلى الملف الذي حددته #nullable enable. سيؤدي ذلك إلى تمكين الافتراض null(للتحذيرات والتعليقات التوضيحية) بشكل كامل في الملف بأكمله (إلا إذا قمت بإيقاف تشغيلها مرة أخرى باستخدام توجيه آخر#nullable) بعد ذلك ، يمكنك الاطلاع على الملف بالكامل والتأكد من أن جميع الكيانات التي يُحتمل أن تكون فارغة يتم التعليق عليها على أنها تسمح null(أي الإضافة ?) ، ثم التعامل مع التحذيرات في هذا الملف ، إن وجدت.

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

هناك أوقات لا يمكن فيها حل المشكلات بهذه الطريقة المباشرة. لذلك ، في بعض السيناريوهات المتعلقة بالتسلسل (على سبيل المثال ، عند استخدام Json.NET أو Entity Framework) ، قد يكون العمل أكثر صعوبة. أعتقد أن هذه المشكلة تستحق مقالة منفصلة.

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

All Articles