
ليس سراً أن Microsoft تعمل على إصدار الإصدار الثامن من C # لبعض الوقت. في الإصدار الأخير من Visual Studio 2019 ، يتوفر إصدار جديد من اللغة (C # 8.0) بالفعل ، ولكن حتى الآن فقط كإصدار تجريبي. تحتوي خطط هذا الإصدار الجديد على العديد من الميزات ، التي قد لا يبدو تنفيذها واضحًا تمامًا ، أو بالأحرى ، ليس متوقعًا تمامًا. أحد هذه الابتكارات هو القدرة على استخدام أنواع Nullable Reference. المعنى المعلن لهذا الابتكار هو مكافحة استثناءات Null Reference (NRE).
يسرنا أن اللغة تتطور وأن الميزات الجديدة يجب أن تساعد المطورين. من قبيل الصدفة ، في محلل PVS-Studio الخاص بنا لـ C # ، توسعت قدرات اكتشاف NRE نفسها في الكود مؤخرًا نسبيًا. وقد سألنا أنفسنا - هل هناك أي معنى الآن للمحللين الاستاتيكيين بشكل عام ، وعلى PVS-Studio على وجه الخصوص ، لمحاولة البحث عن ترجمات مرجعية محتملة للمراجع الفارغة ، إذا ، على الأقل في الكود الجديد باستخدام Nullable Reference ، فإن إلغاء التسجيل هذا سيكون "مستحيلًا" ؟ دعنا نحاول الإجابة على هذا السؤال.
إيجابيات وسلبيات الابتكار
بادئ ذي بدء ، تجدر الإشارة إلى أنه في أحدث إصدار تجريبي من C # 8.0 ، المتوفر في وقت كتابة هذا التقرير ، يتم إيقاف تشغيل Nullable Reference بشكل افتراضي ، أي لن يتغير سلوك أنواع المراجع.
ما أنواع مراجع nullable في C # 8.0 إذا قمت بتضمينها؟ هذا هو نفس النوع المرجعي القديم الجيد ، مع اختلاف أنه يجب الآن تمييز متغيرات هذا النوع بـ "؟" (على سبيل المثال
string؟ ) ، على غرار الطريقة التي تمت بها بالفعل لـ
Nullable <T> ، أي أنواع هامة لاغية (مثل
int؟ ). ومع ذلك ، الآن نفس
السلسلة دون؟ بدأ بالفعل في تفسيره كمرجع غير قابل للإلغاء ، أي هذا هو نوع المرجع الذي لا يمكن أن يحتوي متغيره على قيم
خالية .
استثناء Null Reference Exception هو أحد الاستثناءات الأكثر إزعاجًا لأنه لا يذكر سوى القليل عن مصدر المشكلة ، خاصةً إذا كان هناك العديد من الترجمات في صف واحد بالطريقة التي تثير الاستثناء. تبدو القدرة على حظر تمرير
null إلى متغير مرجعي من النوع جيدة ، ولكن إذا
تم نقل قيمة
null السابقة إلى هذه الطريقة ، وتم ربط بعض منطق التنفيذ الإضافي بهذا ، فما الذي يجب علي فعله الآن؟ بالطبع ، يمكنك تمرير قيمة حرفية أو ثابتة أو ببساطة "مستحيلة" بدلاً من القيمة
الخالية ، والتي ، وفقًا لمنطق البرنامج ، لا يمكن تعيينها لهذا المتغير في أي مكان آخر. ومع ذلك ، يمكن استبدال سقوط البرنامج بأكمله بتنفيذ غير صحيح "صامت". لن يكون دائمًا أفضل من رؤية الخطأ على الفور.
وإذا رمي بدلا استثناء؟ يعتبر الاستثناء ذو مغزى في المكان الذي حدث فيه خطأ ما هو أفضل دائمًا من
NRE في مكان ما أعلى أو أقل في المجموعة. لكن من الجيد أن نتحدث عن مشروعنا الخاص ، حيث يمكننا إصلاح المستهلكين وإدراج
كتلة try-catch ، وعند تطوير مكتبة باستخدام المرجع (non) Nullable Reference ، فإننا نتحمل مسؤولية أن تقوم بعض الطرق دائمًا بإرجاع قيمة. وليس دائمًا في الكود الأصلي أنه سيكون (على الأقل بسيطًا) الاستعاضة عن
الإرجاع فارغة لإلقاء استثناء (يمكن أن يتأثر الكثير من الأكواد).
يمكنك تمكين Nullable Reference على مستوى المشروع عن طريق إضافة خاصية
NullableContextOptions مع قيمة
التمكين لها ، أو على مستوى الملف باستخدام التوجيه قبل المعالج:
#nullable enable string cantBeNull = string.Empty; string? canBeNull = null; cantBeNull = canBeNull!;
ستكون الأنواع الآن أكثر مرئية. بتوقيع هذه الطريقة ، من الممكن تحديد سلوكها ، سواء أكان يحتوي على التحقق من عدمه أم لا ، يمكن أن
يُرجع لاغياً أو لا يمكن. الآن ، إذا حاولت الوصول إلى متغير مرجع nullable دون التحقق ، سيقوم المحول البرمجي بإنشاء تحذير.
مناسب تمامًا عند استخدام مكتبات الجهات الخارجية ، ولكن هناك موقف ينطوي على معلومات مضللة محتملة. الحقيقة هي أن تمرير
null لا يزال ممكناً ، على سبيل المثال ، باستخدام عامل التشغيل الجديد null-forgiving (!). أي إنه فقط بمساعدة علامة تعجب واحدة ، يمكنك كسر جميع الافتراضات الإضافية التي سيتم إجراؤها حول واجهة باستخدام هذه المتغيرات:
#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. في وقت كتابة هذا التقرير ، إذا كان هناك العديد من المشاريع في الحل ، عند الانتقال من أحد الأساليب المعلنة في أحد المشروعات إلى متغير مرجعي ، على سبيل المثال من النوع
String ، إلى طريقة من مشروع آخر حيث
يتم تمكين
NullableContextOptions ، سيقرر المترجم أنه بالفعل سلسلة غير قابلة للإلغاء ، ولن تعطي تحذيرا. وهذا على الرغم من العدد الكبير لسمات
[Nullable (1)] التي تمت إضافتها إلى كل حقل وطريقة فئة في كود IL عند تشغيل Nullable Reference
. بالمناسبة ، يجب أن تؤخذ هذه السمات في الاعتبار إذا كنت تعمل مع قائمة من السمات من خلال التفكير ، وتعتمد على وجود تلك السمات التي أضفتها بنفسك فقط.
يمكن أن يحدث هذا الموقف مشاكل إضافية عند تحويل قاعدة رمز كبيرة إلى مرجع Nullable. على الأرجح ستكون هذه العملية تدريجية ، مشروع تلو الآخر. بالطبع ، مع اتباع نهج كفء للتغيير ، يمكنك التبديل تدريجياً إلى وظيفة جديدة ، ولكن إذا كان لديك بالفعل مسودة عمل ، فإن أي تغييرات فيها خطيرة وغير مرغوب فيها (إنها تعمل - لا تلمسها!). لهذا السبب عند استخدام محلل PVS-Studio ، ليست هناك حاجة لتعديل الكود المصدري أو وضع علامة عليه بطريقة أو بأخرى للكشف عن
NREs المحتملة. للتحقق من الأماكن التي يمكن أن يحدث فيها
NullReferenceException ، تحتاج فقط إلى بدء تشغيل المحلل وإلقاء نظرة على تحذيرات V3080. لا حاجة لتغيير خصائص المشروع أو شفرة المصدر. لا حاجة لإضافة توجيهات أو سمات أو عوامل تشغيل. لا حاجة لتغيير التعليمات البرمجية الخاصة بك.
بدعم من أنواع Nullable Reference في محلل PVS-Studio ، واجهنا خيارًا - هل يجب على المحلل أن يفسر متغيرات المرجع غير القابلة للإلغاء على أنها قيم غير صفرية دائمًا؟ بعد دراسة مسألة احتمالات "كسر" هذا الضمان ، توصلنا إلى استنتاج مفاده أنه لا يوجد - لا ينبغي للمحلل القيام بهذا الافتراض. في الواقع ، حتى لو تم استخدام أنواع مرجعية غير قابلة للإلغاء في كل مكان في المشروع ، يمكن للمحلل أن يكمل استخدامها من خلال اكتشاف المواقف التي قد تظهر فيها قيمة
فارغة في مثل هذا المتغير.
كيف يبحث PVS-Studio عن استثناءات مرجع خالية
تراقب آليات تدفق البيانات في محلل C # PVS-Studio القيم المحتملة للمتغيرات أثناء التحليل. على وجه الخصوص ، يقوم برنامج PVS-Studio أيضًا بإجراء تحليل interprocedural ، أي يحاول تحديد القيمة المحتملة التي يتم إرجاعها بواسطة الطريقة ، وكذلك الطرق التي تسمى في هذه الطريقة ، إلخ. من بين أشياء أخرى ، يتذكر المحلل المتغيرات التي يمكن أن تكون
خالية . إذا رأى المحلل في المستقبل إلغاء التسجيل دون التحقق من هذا المتغير ، مرة أخرى ، إما في الكود الحالي الجاري فحصه ، أو داخل الطريقة المذكورة في هذا الرمز ، سيتم إصدار تحذير V3080 حول استثناء Null Reference محتمل.
في الوقت نفسه ، فإن الفكرة الرئيسية التي يقوم عليها هذا التشخيص هي أن المحلل سوف يقسم فقط إذا رأى في مكان ما تعيينًا
لاغياً لمتغير. هذا هو الفرق الرئيسي بين سلوك هذا التشخيص والمحلل المضمّن في المحول البرمجي الذي يعمل مع أنواع Nullable Reference. سوف يقسم المحلل المضمن في المترجم عند أي اختيار لمتغير مرجعي لاغية لم يتم التحقق منه من النوع ، ما لم يتم "خداع" هذا المحلل بالطبع من قبل المشغل! بأي طريقة أخرى ، يمكن استخدام أي محلل على الإطلاق ، خاصةً إذا حددت لنفسك مثل هذا الهدف ، و PVS-Studio ليس استثناءً).
يقسم PVS-Studio فقط إذا كان
لاغيًا (في سياق محلي ، أو قادم من طريقة). في الوقت نفسه ، حتى لو كان المتغير متغيرًا مرجعيًا غير قابل للإلغاء ، فإن سلوك المحلل لن يتغير - بل سيظل أقسمًا إذا رأى أنه قد تمت كتابته. يبدو لنا هذا النهج أكثر صحة (أو على الأقل مناسب للمستخدم محلل) ، منذ ذلك الحين لا يتطلب الأمر "تغطية" الشفرة بأكملها مع
اختبارات فارغة للعثور على dereferences محتملة - كان يمكن القيام بذلك من قبل ، بدون مرجع Nullable ، على سبيل المثال ، بنفس العقود. بالإضافة إلى ذلك ، يمكن الآن استخدام المحلل للتحكم الإضافي في نفس متغيرات المرجع غير القابلة للإلغاء. إذا تم استخدامها "بأمانة" ، ولم يتم تعيينهم مطلقًا - فسيظل المحلل صامتًا. إذا تم تعيين قيمة خالية وتم إلغاء تحديد المتغير دون التحقق ، يحذر المحلل من ذلك بالرسالة V3080:
#nullable enable String GetStr() { return _count > 0 ? _str : null!; } String str = GetStr(); var len = str.Length; <== V3080: Possible null dereference. Consider inspecting 'str'
دعونا نأخذ في الاعتبار بعض الأمثلة لمثل هذا التشخيص للتشخيصات V3080 في كود Roslyn نفسه. لقد
فحصنا هذا المشروع منذ وقت ليس ببعيد ، ولكن هذه المرة سننظر فقط في مشغلات Null Reference Exception التي لم تكن موجودة في المقالات السابقة. دعونا نرى كيف يمكن لمحلل PVS-Studio العثور على إلغاء مرجعية محتملة للمراجع الفارغة ، وكيف يمكن إصلاح هذه الأماكن باستخدام بناء جملة 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 خاليًا في أحد فروع تنفيذ التعليمات البرمجية. ثم
يتم تمرير
chainedTupleType داخل الأسلوب
ConstructTupleUnderlyingType ، ويستخدم هناك مع التحقق من خلال
Debug.Assert . هذا الموقف شائع جدًا في Roslyn ، ومع ذلك ، تجدر الإشارة إلى أنه يتم حذف
Debug.Assert في إصدار إصدار التجميع. لذلك ، ما زال المحلل يعتبر إلغاء التسجيل في أسلوب
ConstructTupleUnderlyingType خطيرًا. بعد ذلك ، نعطي نص هذه الطريقة ، حيث يحدث إلغاء التسجيل:
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; }
ما إذا كان يجب على المحلل أن يأخذ مثل هذا التأكيد في الحسبان هو في الواقع نقطة نقاش (يريد بعض المستخدمين منا القيام بذلك) ، لأن العقود من System.Diagnostics.Contracts ، على سبيل المثال ، يأخذ المحلل في الاعتبار الآن. سأخبركم فقط بمثال صغير عن استخدامنا الفعلي لنفس روسلين في محللنا. في الآونة الأخيرة ،
دعمنا الإصدار الجديد من Visual Studio ، وفي الوقت نفسه
قمنا بتحديث محلل Roslyn إلى الإصدار 3. بعد ذلك ، بدأ المحلل في الانخفاض عند التحقق من رمز معين لم يسبق له تحطيمه. في الوقت نفسه ، بدأ المحلل في الوقوع ليس داخل الكود الخاص بنا ، ولكن داخل الكود الموجود في Roslyn نفسه - لتقع مع استثناء Null Reference. وأظهر المزيد من تصحيح الأخطاء أنه في المكان الذي تقع فيه Roslyn الآن ، بالضبط سطرين أعلاه ، هناك نفس التحقق من
الأخطاء من خلال
Debug.Assert . وهي ، كما نرى ، لم تنقذ.
هذا مثال جيد جدًا على مشاكل Nullable Reference
، لأن المحول البرمجي يعتبر
Debug.Assert تحققًا صالحًا في أي تكوين. بمعنى أنه إذا قمت بتمكين
#nullable enable وتمييز الوسيطة
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 قد
يُرجع فارغًا ، ولكن يتم استخدام النتيجة دون التحقق (
effectRuleset.GeneralDiagnosticOption ). تتم كتابة
نص الأسلوب
WithEffectiveAction ، والذي يمكن أن يُرجع خالية ، إلى متغير
effectRuleset :
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 للأسلوب
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 ، ستتغير المكالمة كما يلي:
#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 قيد التشغيل لا يحمي من الأخطاء المتعلقة بإلغاء تحديد ارتباطات فارغة ، وإذا تم استخدامه بشكل غير صحيح ، فيمكن أن يؤدي إلى هذه القيود. تجدر الإشارة إلى استخدام محلل استاتيكي حديث ، على سبيل المثال PVS-Studio ، الذي يدعم التحليل interprocedural ، كأداة إضافية ، إلى جانب Nullable Reference ، يمكن أن تحميك من إزالة مراجع خالية من المراجع. كل من هذه الأساليب - التحليل المتعمق المتداخل والشروح لتوقيعات الطريقة (التي تجعل مرجع Nullable بشكل أساسي) ، له مزايا وعيوب. سيسمح لك المحلل بالحصول على قائمة بالأماكن التي يحتمل أن تكون خطرة ، وأيضًا عند تغيير رمز موجود ، انظر إلى كل عواقب هذه التغييرات. إذا قمت بتعيين قيمة
فارغة في أي حال ، يجب على المحلل أن يشير على الفور إلى جميع المستهلكين ، حيث لا يتم التحقق منه قبل إلغاء التسجيل.
يمكنك البحث بشكل مستقل عن بعض الأخطاء الأخرى سواء في المشروع المعني أو في المشروع الخاص بك. للقيام بذلك ، تحتاج فقط إلى
تنزيل وتجربة محلل PVS-Studio.

إذا كنت ترغب في مشاركة هذا المقال مع جمهور يتحدث الإنجليزية ، فالرجاء استخدام الرابط الخاص بالترجمة: Paul Eremeev، Alexander Senichkin.
أنواع المرجع Nullable في C # 8.0 والتحليل الثابت