
ليس سراً أن Microsoft تعمل على الإصدار الثامن من لغة C # لفترة طويلة. إصدار اللغة الجديد (C # 8.0) متاح بالفعل في الإصدار الأخير من Visual Studio 2019 ، لكنه لا يزال في مرحلة تجريبية. سيكون لهذا الإصدار الجديد بعض الميزات التي يتم تنفيذها بطريقة غير واضحة إلى حد ما ، أو غير متوقعة إلى حد ما. أنواع المرجع Nullable هي واحدة منها. تم الإعلان عن هذه الميزة كوسيلة لمكافحة استثناءات Null Reference (NRE).
من الجيد رؤية تطور اللغة واكتساب ميزات جديدة لمساعدة المطورين. عن طريق الصدفة ، منذ بعض الوقت ، عززنا بشكل كبير قدرة محلل C # PVS-Studio للكشف عن NREs. والآن نحن نتساءل عما إذا كان يجب على المحللين الاستاتيكيين بشكل عام و PVS-Studio على وجه الخصوص أن يزعجوا تشخيص الأمراض التي لاغية محتملة لأنه ، على الأقل في الكود الجديد الذي سيستخدم Nullable Reference ، ستصبح مثل هذه القيود "مستحيلة"؟ دعونا نحاول توضيح ذلك.
إيجابيات وسلبيات الميزة الجديدة
تذكير واحد قبل المتابعة: أحدث إصدار تجريبي من C # 8.0 ، المتوفر حتى كتابة هذا المنشور ، تم تعطيل أنواع Nullable Reference به افتراضيًا ، أي لم يتغير سلوك أنواع المراجع.
إذن ما أنواع المراجع الفارغة في C # 8.0 إذا قمنا بتمكين هذا الخيار؟ إنها أساسًا نفس أنواع المراجع القديمة الجيدة باستثناء أنه سيتعين عليك الآن إضافة "؟" بعد اسم الكتابة (على سبيل المثال ،
string؟ ) ، على غرار
Nullable <T> ، أي أنواع القيم الخالية (مثل ،
int؟ ). بدون "؟" ، سيتم الآن تفسير نوع
السلسلة الخاص بنا كمرجع غير قابل للإلغاء ، أي نوع مرجع لا يمكن تعيينه
فارغًا .
يعد Null Reference Exception أحد الاستثناءات الأكثر إثارة للدغدغة في البرنامج لأنه لا يوضح الكثير عن مصدره ، خاصة إذا كانت طريقة الإلقاء تحتوي على عدد من عمليات dereference في صف واحد. تبدو القدرة على حظر التعيين الفارغ لمتغير من النوع المرجعي باردة ، ولكن ماذا عن تلك الحالات التي يكون فيها تمرير
لاغٍ إلى طريقة ما منطقيًا على التنفيذ؟ بدلاً من أن تكون
لاغية ، يمكننا بالطبع استخدام قيمة حرفية أو ثابتة أو ببساطة "مستحيلة" لا يمكن تعيينها بشكل منطقي للمتغير في أي مكان آخر. ولكن هذا ينطوي على خطر استبدال تعطل البرنامج بـ "صامت" ، ولكن التنفيذ غير الصحيح ، والذي غالباً ما يكون أسوأ من مواجهة الخطأ على الفور.
ماذا عن رمي استثناء بعد ذلك؟ يعتبر الاستثناء ذو المغزى في موقع حدث فيه خطأ ما أفضل دائمًا من
NRE في مكان ما أعلى أو أسفل المكدس. لكنه جيد فقط في مشروعك الخاص ، حيث يمكنك تصحيح المستهلكين عن طريق إدراج كتلة
try-catch وهي فقط مسؤوليتك. عند تطوير مكتبة باستخدام (غير) Nullable Reference ، نحتاج إلى ضمان أن طريقة معينة تُرجع دائمًا قيمة. بعد كل شيء ، ليس من الممكن دائمًا (أو على الأقل سهل) حتى في التعليمات البرمجية الخاصة بك لاستبدال إرجاع
فارغة مع رمي استثناء (لأنه قد يؤثر على الكثير من التعليمات البرمجية).
يمكن تمكين مرجع Nullable إما على مستوى المشروع العمومي عن طريق إضافة خاصية
NullableContextOptions مع
تمكين القيمة
، أو على مستوى الملف عن طريق التوجيه قبل المعالج:
#nullable enable string cantBeNull = string.Empty; string? canBeNull = null; cantBeNull = canBeNull!;
ميزة Nullable Reference ستجعل الأنواع أكثر إفادة. يمنحك توقيع الأسلوب فكرة عن سلوكه: إذا كان يحتوي على فحص فارغ أم لا ، إذا كان يمكن إرجاع
فارغة أم لا. الآن ، عند محاولة استخدام متغير مرجع nullable دون التحقق منه ، سيصدر المحول البرمجي تحذيرًا.
يعد هذا أمرًا ملائمًا عند استخدام مكتبات الجهات الخارجية ، لكنه يضيف أيضًا مخاطرة تضليل مستخدم المكتبة ، حيث لا يزال من الممكن تجاوز قيمة
null باستخدام عامل التشغيل الجديد الصفح (!). بمعنى أن إضافة علامة تعجب واحدة يمكن أن تؤدي إلى كسر جميع الافتراضات الأخرى حول الواجهة باستخدام هذه المتغيرات:
#nullable enable String GetStr() { return _count > 0 ? _str : null!; } String str = GetStr(); var len = str.Length;
نعم ، يمكنك القول أن هذه برمجة سيئة ولن يقوم أحد بكتابة كود مثل هذا من أجل حقيقي ، ولكن طالما كان من الممكن القيام بذلك ، لا يمكنك الشعور بالأمان بالاعتماد فقط على العقد الذي تفرضه واجهة أسلوب معين ( قائلا أنه لا يمكن العودة
فارغة ).
بالمناسبة ، يمكنك كتابة نفس الكود باستخدام عدة
! عوامل التشغيل ، حيث يتيح لك C # الآن القيام بذلك (وهذه الشفرة قابلة للتجميع بشكل مثالي):
cantBeNull = canBeNull!!!!!!!;
من خلال الكتابة بهذه الطريقة ، نؤكد على فكرة "انظروا ، قد يكون هذا
باطلاً !!!" (نحن في فريقنا ، نسمي هذا البرمجة "العاطفية"). في الواقع ، عند بناء شجرة بناء الجملة ، يقوم المترجم (من Roslyn) بترجمة
! عامل التشغيل بنفس طريقة تفسيره للأقواس العادية ، مما يعني أنه يمكنك كتابة أكبر عدد ممكن
! كما تريد - تمامًا مثل الأقواس. ولكن إذا كتبت ما يكفي منهم ، فيمكنك "إيقاف" المترجم. ربما سيتم إصلاح ذلك في الإصدار النهائي من C # 8.0.
وبالمثل ، يمكنك الالتفاف على تحذير المحول البرمجي عند الوصول إلى متغير مرجع nullable دون التحقق:
canBeNull!.ToString();
دعنا نضيف المزيد من العواطف:
canBeNull!!!?.ToString();
بالكاد سترى بناء جملة مثل هذا في الكود الحقيقي بالرغم من ذلك. من خلال كتابة عامل التشغيل الذي
لاغٍ ، نقول للمترجم ، "هذا الرمز على ما يرام ، تحقق من عدم الحاجة." بإضافة عامل التشغيل Elvis نقول له ، "أو ربما لا ؛ دعونا التحقق من ذلك فقط في حالة ".
الآن ، يمكنك أن تسأل بشكل معقول لماذا لا يزال بإمكانك تعيين قيمة
خالية لمتغيرات أنواع المرجع غير القابلة للإلغاء بهذه السهولة إذا كان مفهوم هذا النوع يعني أن هذه المتغيرات لا يمكن أن تكون لها قيمة
فارغة ؟ الجواب هو أنه "تحت الغطاء" ، على مستوى كود IL ، لا يزال نوع المرجع غير القابل للإلغاء لدينا ... هو نوع المرجع القديم "العادي" الجيد ، كما أن بناء جملة nullability بأكمله هو مجرد تعليق توضيحي لبنية برنامج التحويل البرمجي المدمجة محلل (الذي نعتقد أنه ليس مناسبًا تمامًا للاستخدام ، لكني سأشرح ذلك لاحقًا). شخصياً ، لم نعثر على حل "أنيق" لإدراج بناء الجملة الجديد كتعليق توضيحي لأداة تابعة لجهة خارجية (حتى مضمنة في المترجم) لأن حقيقة أن هذا مجرد تعليق توضيحي قد لا يكون واضحًا على الإطلاق بالنسبة للمبرمج ، حيث أن بناء الجملة هذا يشبه إلى حد كبير بناء الجملة للهيئات الفارغة ولكنه يعمل بطريقة مختلفة تمامًا.
العودة إلى طرق أخرى لكسر أنواع Nullable Reference. اعتبارًا من لحظة كتابة هذه المقالة ، عندما يكون لديك حل يتكون من عدة مشاريع ، فتمرير متغير من نوع مرجع ، على سبيل المثال ،
سلسلة من طريقة تم الإعلان عنها في مشروع إلى طريقة في مشروع آخر يحتوي على
NullableContext يفترض المترجم أنه يتعامل مع سلسلة غير قابلة للإلغاء وسوف يبقى المترجم صامتًا. وهذا على الرغم من الأطنان من سمات
[Nullable (1)] التي تمت إضافتها إلى كل حقل وطريقة في رمز IL عند تمكين Nullable References
. بالمناسبة ، يجب أن تؤخذ هذه السمات في الاعتبار إذا كنت تستخدم انعكاسًا للتعامل مع السمات وتفترض أن الكود لا يحتوي إلا على السمات المخصصة الخاصة بك.
قد يتسبب هذا الموقف في حدوث مشكلات إضافية عند تكييف قاعدة رمز كبيرة مع نمط Nullable Reference. من المحتمل أن تستمر هذه العملية لفترة من الوقت ، كل مشروع على حدة. إذا كنت حريصًا ، بالطبع ، يمكنك دمج الميزة الجديدة تدريجيًا ، ولكن إذا كان لديك بالفعل مشروع يعمل ، فإن أي تغييرات عليه تكون خطيرة وغير مرغوب فيها (إذا كانت تعمل ، فلا تلمسها!). لهذا السبب تأكدنا من أنك لست مضطرًا إلى تعديل شفرة المصدر أو وضع علامة عليها لاكتشاف
NRE المحتملة عند استخدام محلل PVS-Studio. للتحقق من المواقع التي يمكن أن تلقي
NullReferenceException ، ما عليك سوى تشغيل المحلل والبحث عن تحذيرات V3080. لا حاجة لتغيير خصائص المشروع أو شفرة المصدر. لا حاجة لإضافة توجيهات أو سمات أو عوامل تشغيل. لا حاجة لتغيير الكود القديم.
عند إضافة دعم Nullable Reference إلى PVS-Studio ، كان علينا أن نقرر ما إذا كان يجب على المحلل أن يفترض أن متغيرات أنواع المرجع غير القابلة للإلغاء تحتوي دائمًا على قيم غير فارغة. بعد التحقق من الطرق التي يمكن بها كسر هذا الضمان ، قررنا أن PVS-Studio يجب ألا يقوم بهذا الافتراض. بعد كل شيء ، حتى إذا كان المشروع يستخدم أنواع مرجعية غير قابلة للإلغاء طوال الطريق ، فيمكن للمحلل إضافة هذه الميزة من خلال الكشف عن تلك المواقف المحددة التي يمكن أن يكون لهذه المتغيرات فيها قيمة
خالية .
كيف يبحث PVS-Studio عن استثناءات مرجع خالية
تتبع آليات تدفق البيانات في محلل PVS-Studio's C # القيم المحتملة للمتغيرات أثناء عملية التحليل. يتضمن هذا أيضًا تحليل interprocedural ، أي تتبع القيم المحتملة التي يتم إرجاعها بواسطة طريقة وأساليبها المتداخلة وما إلى ذلك. بالإضافة إلى ذلك ، يتذكر PVS-Studio المتغيرات التي يمكن تعيين قيمة
فارغة لها . كلما رأى أن هذا المتغير يتم إلغاء تسجيله بدون فحص ، سواء أكان في الكود الحالي قيد التحليل ، أو داخل طريقة تم استدعاؤها في هذا الكود ، فإنه سيصدر تحذير V3080 حول استثناء مرجعي Null محتمل.
الفكرة وراء هذا التشخيص هي أن تغضب المحلل فقط عندما ترى مهمة
لاغية . هذا هو الاختلاف الرئيسي في سلوك تشخيصنا عن سلوك المحلل المدمج في المترجم الذي يتعامل مع أنواع Nullable Reference. سوف يشير المحلل المضمن إلى كل إشارة مرجعية لمتغير مرجعي لاغية غير قابل للتحقق - بالنظر إلى أنه لم يتم تضليله باستخدام
! عامل التشغيل أو حتى مجرد فحص معقد (تجدر الإشارة إلى أن أي محلل ثابت تمامًا ، نظرًا لأن PVS-Studio ليس استثناءً هنا ، يمكن "تضليله" بطريقة أو بأخرى ، خاصةً إذا كنت تنوي القيام بذلك).
PVS-Studio ، من ناحية أخرى ، يحذرك فقط إذا كان
لاغياً (سواء في السياق المحلي أو في سياق طريقة خارجية). حتى إذا كان المتغير من نوع مرجع غير قابل للإلغاء ، فسوف يستمر المحلل في الإشارة إليه إذا
رأى تعيينًا
فارغًا لهذا المتغير. نعتقد أن هذا النهج أكثر ملاءمة (أو على الأقل أكثر ملاءمة للمستخدم) لأنه لا يطالب بـ "تلطيخ" الكود بالكامل مع عمليات التحقق الفارغة لتتبع dereferences المحتملة - بعد كل هذا ، كان هذا الخيار متاحًا حتى قبل Nullable Reference تم تقديمها ، على سبيل المثال ، من خلال استخدام العقود. ما هو أكثر من ذلك ، يمكن للمحلل الآن توفير سيطرة أفضل على المتغيرات المرجعية غير القابلة للإلغاء بأنفسهم. إذا تم استخدام مثل هذا المتغير "إلى حد ما" ولم يتم تعيينه أبداً ، فلن يقول PVS-Studio كلمة واحدة. إذا تم تعيين المتغير
فارغًا ثم تم إلغاء تحديده دون فحص مسبق ، فسيقوم PVS-Studio بإصدار تحذير V3080:
#nullable enable String GetStr() { return _count > 0 ? _str : null!; } String str = GetStr(); var len = str.Length; <== V3080: Possible null dereference. Consider inspecting 'str'
الآن ، دعونا نلقي نظرة على بعض الأمثلة التي توضح كيف يتم تشغيل هذا التشخيص بواسطة رمز Roslyn نفسه. لقد قمنا بالفعل
بفحص هذا المشروع مؤخرًا ، لكننا سنبحث هذه المرة فقط في استثناءات Null Reference المحتملة غير المذكورة في المقالات السابقة. سنرى كيف يكتشف PVS-Studio NREs المحتملة وكيف يمكن إصلاحها باستخدام بناء جملة Nullable Reference الجديد.
V3080 [CWE-476] dereference null الممكنة داخل الأسلوب. النظر في فحص الوسيطة الثانية: chainedTupleType. Microsoft.CodeAnalysis.CSharp TupleTypeSymbol.cs 244 NamedTypeSymbol chainedTupleType; if (_underlyingType.Arity < TupleTypeSymbol.RestPosition) { .... chainedTupleType = null; } else { .... } return Create(ConstructTupleUnderlyingType(firstTupleType, chainedTupleType, newElementTypes), elementNames: _elementNames);
كما ترون ، يمكن تعيين متغير
chainedTupleType القيمة
الخالية في أحد فروع التنفيذ. ثم يتم تمريره إلى الأسلوب
ConstructTupleUnderlyingType ويستخدم هناك بعد التحقق
Debug.Assert . إنه نمط شائع جدًا في Roslyn ، لكن ضع في اعتبارك أنه تمت إزالة
Debug.Assert في إصدار الإصدار. لهذا السبب لا يزال المحلل يعتبر أن dereference داخل طريقة
ConstructTupleUnderlyingType خطير. فيما يلي نص هذه الطريقة ، حيث يتم إجراء dereference:
internal static NamedTypeSymbol ConstructTupleUnderlyingType( NamedTypeSymbol firstTupleType, NamedTypeSymbol chainedTupleTypeOpt, ImmutableArray<TypeWithAnnotations> elementTypes) { Debug.Assert (chainedTupleTypeOpt is null == elementTypes.Length < RestPosition); .... while (loop > 0) { .... currentSymbol = chainedTupleTypeOpt.Construct(chainedTypes); loop--; } return currentSymbol; }
إنها في الواقع مسألة محل خلاف حول ما إذا كان يجب على المحلل أن يأخذ Asserts مثل ذلك في الاعتبار (يريد بعض المستخدمين منا القيام بذلك) - بعد كل شيء ، يأخذ المحلل العقود من System.Diagnostics.Contracts بعين الاعتبار. إليك مثال صغير حقيقي على الحياة من تجربتنا في استخدام Roslyn في محللنا الخاص. أثناء
إضافة دعم أحدث إصدار من Visual Studio مؤخرًا ، قمنا أيضًا بتحديث Roslyn إلى الإصدار الثالث. بعد ذلك ، بدأ PVS-Studio في التعطل على رمز معين لم يسبق له التعطل عليه من قبل. لن يحدث التعطل ، المصحوب باستثناء Null Reference ، في التعليمات البرمجية الخاصة بنا ولكن في رمز Roslyn. كشف تصحيح الأخطاء أن جزء التعليمات البرمجية حيث تعطلت Roslyn الآن كان يحتوي على هذا النوع من
Debug.Assert القائم على التحقق من عدة أسطر أعلى - ومن الواضح أن التحقق لم يساعد.
إنه مثال بياني لكيفية حدوث مشكلة مع Nullable Referencebecause من المترجم الذي يعالج
Debug.Assert كتحقق موثوق به في أي تكوين. أي إذا قمت بإضافة
# nullableتم تمكين ووضع علامة على الوسيطة
chainedTupleTypeOpt كمرجع nullable
، فلن يصدر المترجم أي تحذير على dereference داخل أسلوب
ConstructTupleUnderlyingType .
الانتقال إلى أمثلة أخرى من التحذيرات بواسطة PVS-Studio.
V3080 dereference ممكن. النظر في تفتيش "فعالةRuleset". RuleSet.cs 146 var effectiveRuleset = ruleSet.GetEffectiveRuleSet(includedRulesetPaths); effectiveRuleset = effectiveRuleset.WithEffectiveAction(ruleSetInclude.Action); if (IsStricterThan(effectiveRuleset.GeneralDiagnosticOption, ....)) effectiveGeneralOption = effectiveRuleset.GeneralDiagnosticOption;
يشير هذا التحذير إلى أن استدعاء الأسلوب
WithEffectiveAction قد تُرجع
لاغية ، بينما لم يتم التحقق من قيمة الإرجاع المعينة للمتغير
الفعال Ruleset قبل الاستخدام (
effectRuleset.GeneralDiagnosticOption ). إليك
نص أسلوب
WithEffectiveAction :
public RuleSet WithEffectiveAction(ReportDiagnostic action) { if (!_includes.IsEmpty) throw new ArgumentException(....); switch (action) { case ReportDiagnostic.Default: return this; case ReportDiagnostic.Suppress: return null; .... return new RuleSet(....); default: return null; } }
مع تمكين Nullable Reference للأسلوب
GetEffectiveRuleSet ، سنحصل على موقعين حيث يجب تغيير سلوك الكود. نظرًا لأن الطريقة الموضحة أعلاه يمكن أن تطرح استثناءًا ، فمن المنطقي افتراض أن الدعوة إليه ملفوفة في كتلة
try-catch وأنه سيكون من الصحيح إعادة كتابة الطريقة لرمي استثناء بدلاً من إرجاع قيمة
خالية . ومع ذلك ، إذا قمت بتتبع بعض المكالمات مرة أخرى ، فسترى أن رمز الالتقاط بعيد جدًا بحيث يتعذر التنبؤ بالنتائج. دعونا نلقي نظرة على المستهلك للمتغير
الفعال Ruleset ، طريقة
IsStricterThan :
private static bool IsStricterThan(ReportDiagnostic action1, ReportDiagnostic action2) { switch (action2) { case ReportDiagnostic.Suppress: ....; case ReportDiagnostic.Warn: return action1 == ReportDiagnostic.Error; case ReportDiagnostic.Error: return false; default: return false; } }
كما ترون ، إنها عبارة عن عبارة تبديل بسيطة تختارها بين عددين ، مع
ReportDiagnostic.Default كقيمة افتراضية. لذلك سيكون من الأفضل إعادة كتابة المكالمة على النحو التالي:
سيتم تغيير توقيع
WithEffectiveAction :
#nullable enable public RuleSet? WithEffectiveAction(ReportDiagnostic action)
هذا ما ستبدو عليه المكالمة:
RuleSet? effectiveRuleset = ruleSet.GetEffectiveRuleSet(includedRulesetPaths); effectiveRuleset = effectiveRuleset?.WithEffectiveAction(ruleSetInclude.Action); if (IsStricterThan(effectiveRuleset?.GeneralDiagnosticOption ?? ReportDiagnostic.Default, effectiveGeneralOption)) effectiveGeneralOption = effectiveRuleset.GeneralDiagnosticOption;
نظرًا لأن
IsStricterThan لا يؤدي إلا المقارنة ، فيمكن إعادة كتابة الشرط - على سبيل المثال ، مثل هذا:
if (effectiveRuleset == null || IsStricterThan(effectiveRuleset.GeneralDiagnosticOption, effectiveGeneralOption))
المثال التالي.
V3080 dereference ممكن. النظر في تفتيش "propertySymbol". BinderFactory.BinderFactoryVisitor.cs 372 var propertySymbol = GetPropertySymbol(parent, resultBinder); var accessor = propertySymbol.GetMethod; if ((object)accessor != null) resultBinder = new InMethodBinder(accessor, resultBinder);
لإصلاح هذا التحذير ، نحتاج إلى معرفة ما يحدث لمتغير
propertySymbol التالي.
private SourcePropertySymbol GetPropertySymbol( BasePropertyDeclarationSyntax basePropertyDeclarationSyntax, Binder outerBinder) { .... NamedTypeSymbol container = GetContainerType(outerBinder, basePropertyDeclarationSyntax); if ((object)container == null) return null; .... return (SourcePropertySymbol)GetMemberSymbol(propertyName, basePropertyDeclarationSyntax.Span, container, SymbolKind.Property); }
الأسلوب
GetMemberSymbol ، أيضًا ، يمكن إرجاع
فارغة ضمن شروط معينة.
private Symbol GetMemberSymbol( string memberName, TextSpan memberSpan, NamedTypeSymbol container, SymbolKind kind) { foreach (Symbol sym in container.GetMembers(memberName)) { if (sym.Kind != kind) continue; if (sym.Kind == SymbolKind.Method) { .... var implementation = ((MethodSymbol)sym).PartialImplementationPart; if ((object)implementation != null) if (InSpan(implementation.Locations[0], this.syntaxTree, memberSpan)) return implementation; } else if (InSpan(sym.Locations, this.syntaxTree, memberSpan)) return sym; } return null; }
مع تمكين أنواع المراجع الخالية ، سيتم تغيير المكالمة إلى هذا:
#nullable enable SourcePropertySymbol? propertySymbol = GetPropertySymbol(parent, resultBinder); MethodSymbol? accessor = propertySymbol?.GetMethod; if ((object)accessor != null) resultBinder = new InMethodBinder(accessor, resultBinder);
من السهل إصلاحه عندما تعرف مكانه. يمكن أن يكتشف التحليل الثابت هذا الخطأ المحتمل دون أي جهد عن طريق جمع جميع قيم الحقل الممكنة من جميع سلاسل استدعاء الإجراء.
V3080 dereference ممكن. النظر في تفتيش "simpleName". CSharpCommandLineParser.cs 1556 string simpleName; simpleName = PathUtilities.RemoveExtension( PathUtilities.GetFileName(sourceFiles.FirstOrDefault().Path)); outputFileName = simpleName + outputKind.GetDefaultExtension(); if (simpleName.Length == 0 && !outputKind.IsNetModule()) ....
المشكلة في السطر مع الاختيار
simpleName.Length . ينتج
simpleName المتغير عن تنفيذ سلسلة طويلة من الأساليب ويمكن تعيينه
فارغًا . بالمناسبة ، إذا كنت فضوليًا ، يمكنك إلقاء نظرة على طريقة
RemoveExtension لمعرفة مدى اختلافها عن
Path.GetFileNameWithoutExtension. simpleName! = سيكون اختيار
Null كافياً ، ولكن مع أنواع المرجع غير القابلة للإلغاء ، سيتغير الرمز إلى شيء مثل هذا:
#nullable enable public static string? RemoveExtension(string path) { .... } string simpleName;
هذا ما قد تبدو عليه المكالمة:
simpleName = PathUtilities.RemoveExtension( PathUtilities.GetFileName(sourceFiles.FirstOrDefault().Path)) ?? String.Empty;
استنتاج
يمكن أن تكون أنواع المراجع Nullable مساعدة كبيرة عند تصميم البنية من الصفر ، ولكن إعادة صياغة الكود الحالي قد يتطلب الكثير من الوقت والعناية ، حيث قد يؤدي ذلك إلى عدد من الأخطاء المراوغة. لا تهدف هذه المقالة إلى تثبيطك عن استخدام أنواع Nullable Reference. نجد أن هذه الميزة الجديدة مفيدة بشكل عام على الرغم من أن الطريقة الدقيقة لتطبيقها قد تكون مثيرة للجدل.
ومع ذلك ، تذكر دائمًا القيود المفروضة على هذا النهج وتذكر أن تمكين وضع Nullable Reference لا يحميك من NREs وأنه عند سوء الاستخدام ، يمكن أن يصبح هو نفسه مصدر هذه الأخطاء. نوصي باستكمال ميزة Nullable Reference بأداة تحليل ثابتة حديثة ، مثل PVS-Studio ، والتي تدعم التحليل المتداخل لحماية برنامجك من NREs. كل من هذه الأساليب - تحليلات interprocedural العميقة وتوقيعات طريقة التعليق (وهو ما يفعله في الواقع وضع Nullable Reference) - لها إيجابيات وسلبيات. سوف يزودك المحلل بقائمة بالمواقع التي يحتمل أن تكون خطرة ويسمح لك بمشاهدة عواقب تعديل التعليمات البرمجية الموجودة. إذا كانت هناك مهمة فارغة في مكان ما ، فسوف يشير المحلل إلى كل مستهلك للمتغير حيث يتم إلغاء ترجمته دون تدقيق.
يمكنك التحقق من هذا المشروع أو المشاريع الخاصة بك بحثًا عن عيوب أخرى - ما عليك سوى
تنزيل PVS-Studio وتجربته.