Menu
flag Ar iconالعربيةdown icon

الأنواع القابلة لـ null في C#: int? ودمج null بـ ?? و?. وفحوص null

كل شيء عن null في C#: أنواع القيم القابلة لـ null (int? وNullable<T>) مع HasValue وValue وGetValueOrDefault، ومعاملا دمج null ?? و??=، والمعامل الشرطي لـ null ?.، وفحوص null، وأنواع المراجع القابلة لـ null (string?) من C# 8.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

تعني null "لا قيمة". استطاعت الأنواع المرجعية (الأصناف والنصوص والمصفوفات) دائمًا حملها. أما أنواع القيم (int وdecimal وbool وDateTime والبنى) فلا تستطيع، ما لم تجعلها قابلة لـ null بـ ?. ثم تمنحك C# مجموعة صغيرة من المعاملات للعمل مع القيم التي قد تكون مفقودة: ?? و??= و?..

أنواع القيم القابلة لـ null: int?

أضف ? إلى نوع قيمة لتسمح بـ null. int? اختصار للبنية Nullable<int>، التي تحمل قيمة مع علامة تقول هل القيمة موجودة:

المخرجات:

False
True
True
5.50
0
3
Rated 4 stars
4

المصادر النموذجية لأنواع القيم القابلة لـ null هي حقول النماذج الاختيارية، ومعاملات الاستعلام، وأعمدة NULL في قاعدة بيانات: Order مع DateTime? ShippedAt لم يُشحن ما دامت القيمة null. البديل، قيمة سحرية مثل -1 أو DateTime.MinValue، يبدو كبيانات حقيقية ويُستخدم كذلك عاجلًا أو آجلًا.

يتحوّل int? إلى int صراحة فقط، لأن التحويل قد يفشل. وهذا يقودنا إلى الاستثناء الوحيد الذي ترميه أنواع القيم القابلة لـ null.

"Nullable object must have a value"

قراءة .Value حين لا توجد قيمة، أو تحويل int? قيمته null إلى int، يرمي InvalidOperationException:

المخرجات:

InvalidOperationException: Nullable object must have a value.
0
0
not stocked

stock is int count نمط: يختبر وجود قيمة ويستخرجها في count في خطوة واحدة، وهذا أسهل قراءة من HasValue مع Value. في صفحة مطابقة الأنماط أشكال أخرى.

الحساب والمقارنات مع null

المعاملات على أنواع القيم القابلة لـ null مرفوعة (lifted): إن كان أي معامل null تكون النتيجة null. مقارنات الترتيب (< و> و<= و>=) هي الاستثناء: تعيد false بدل null. (والاستثناء الآخر bool? مع & و|، حيث null & false تساوي false.) مثلًا:

المخرجات:

True
[]
False
False
True
True

كل من a > 5 وa <= 5 خاطئ، فلا تكون !(a > 5) هي a <= 5 حين تكون a هي null. شيفرة مثل if (!(age >= 18)) Deny(); تعامل العمر المفقود كقاصر، وقد يكون أو لا يكون هذا ما قصدته. قرّر أولًا ما يجب أن تعنيه null، عادة بـ ??، ثم قارن.

معامل دمج null ??

يُقيَّم a ?? b إلى a حين لا تكون a هي null، وإلى b في غير ذلك. يعمل مع أنواع القيم القابلة لـ null ومع الأنواع المرجعية، ويتسلسل بترتيب كتابته:

المخرجات:

dark
16px
20
ANONYMOUS

لا يُقيَّم الطرف الثاني إلا عند الحاجة، فلا تلمس cache ?? LoadFromDatabase() قاعدة البيانات حين يكون للذاكرة المؤقتة قيمة. ويقبل ?? أيضًا تعبير throw، وهذا يصنع حارسًا مضغوطًا (راجع throw):

this.repository = repository ?? throw new ArgumentNullException(nameof(repository));

??= (C# 8)

تُسند x ??= y القيمة y إلى x فقط إن كانت x هي null. وهي الأسلوب المعتاد للتهيئة الكسولة:

private List<string> tags;

public void AddTag(string tag)
{
    tags ??= new List<string>();   // C# 8 and later
    tags.Add(tag);
}

قبل C# 8 اكتب tags = tags ?? new List<string>(); أو if (tags == null) tags = new List<string>();.

المعامل الشرطي لـ null ?.

يقيّم a?.Member العضو Member فقط إن لم تكن a هي null؛ وإلا يكون التعبير كله null. يحوّل سلسلة فحوص null إلى تعبير واحد:

المخرجات:

Braga
(no city)
(no customer)
0
True
handler: saved

تفاصيل تستحق المعرفة:

  • المعامل يقطع بقية السلسلة: في nobody?.Address?.City بمجرد أن تكون nobody هي null لا يُنفَّذ شيء بعدها.
  • حين يعيد العضو نوع قيمة تصبح النتيجة قابلة لـ null: Orders?.Count من نوع int?، ولهذا تحتاج إلى ?? 0 لتعود int.
  • لا يمكن أن تكون ?. هدف إسناد قبل C# 14: لا تُترجم customer?.Name = "x" في الإصدارات الأقدم.
  • handler?.Invoke(...) هي الطريقة القياسية لإطلاق حدث أو استدعاء دالة استدعاء راجع اختيارية.

استخدم ?. حيث تكون null حالة مشروعة. نثرها في كل مكان لتجنّب الاستثناءات يخفي الخطأ الذي أنتج null غير المتوقعة، ويستمر البرنامج ببيانات مفقودة بدل أن يفشل حيث المشكلة.

فحص null

if (customer == null) { ... }             // classic
if (customer is null) { ... }             // C# 7 constant pattern
if (customer != null) { ... }
if (customer is not null) { ... }         // C# 9
if (customer is { } c) { ... }            // C# 8 property pattern: not null, and named

تنفّذ is null دائمًا فحص null حقيقيًا، بينما تستدعي == التحميل الزائد لمعامل النوع إن وُجد، فيمكن لتحميل سيئ الكتابة أن يجعل x == null تكذب. وللوسائط التي يجب ألا تكون null افشل مبكرًا:

public void Ship(Order order)
{
    ArgumentNullException.ThrowIfNull(order);   // .NET 6+
    // ...
}

أنواع المراجع القابلة لـ null (C# 8)

إضافة ? إلى نوع قيمة تغيّر النوع. أما إضافة ? إلى نوع مرجعي، منذ C# 8، فلا تغيّر إلا ما يفحصه المترجم. مع تفعيل الميزة (<Nullable>enable</Nullable> في ملف المشروع، الافتراضي للمشاريع الجديدة منذ .NET 6) تعني string "يجب ألا تكون null أبدًا" وتعني string? "قد تكون null"، ويحذّر المترجم حين لا تتطابق الشيفرة:

#nullable enable

public class User
{
    public string Email { get; set; }      // warning CS8618: Non-nullable property 'Email'
                                           // must contain a non-null value when exiting constructor.
    public string? Nickname { get; set; }  // allowed to be null

    public int NicknameLength() => Nickname.Length;
    // warning CS8602: Dereference of a possibly null reference.

    public int SafeLength() => Nickname?.Length ?? 0;   // no warning
}

string? input = Console.ReadLine();
string name = input;                       // warning CS8600: Converting null literal or possible
                                           // null value to non-nullable type.
string checkedName = input ?? "guest";     // no warning
int len = input!.Length;                   // ! tells the compiler "trust me": no warning

يتتبّع المترجم فحوصك عبر الدالة: بعد if (input != null) تُحسب input غير null. ويُسكت المعامل ! (null-forgiving) تحذيرًا حين تعرف أفضل من التحليل؛ وكل واحد منه ادعاء لا يستطيع المترجم التحقق منه، فأبقها نادرة.

حقيقتان تزيلان معظم الالتباس. أولًا، هذه تحذيرات فقط: يعمل البرنامج بالطريقة نفسها، وما زال string يمكن أن يحمل null وقت التشغيل إن جاء من شيفرة بلا وسوم. ثانيًا، string? ليست Nullable<string>؛ لا HasValue فيها.

أخطاء شائعة

  • .Value دون فحص. يرمي InvalidOperationException عند null. استخدم ?? أو GetValueOrDefault() أو is int n.
  • افتراض أن !(x > 5) تعني x <= 5. ليس حين تكون x هي null: كل مقارنة ترتيب مع null خاطئة.
  • تغطية كل null بـ ?.. يسكت العرض ويبعد الفشل عن السبب.
  • معاملة تحذيرات المراجع القابلة لـ null كضجيج. كل واحد منها يعلّم سطرًا قد يرمي NullReferenceException. أصلح التدفق أو صرّح بالقصد بـ ?.
  • قيم سحرية بدل null. -1 و0 وDateTime.MinValue بمعنى "غير معروف" تبدو كبيانات حقيقية. استخدم int? وDateTime?.

الأسئلة الشائعة

ماذا تعني int? في C#؟

int? اختصار لـ Nullable<int>: عدد int يمكن أن يكون أيضًا null. أنواع القيم مثل int وdecimal وbool وDateTime تحمل عادة قيمة دائمًا؛ وإضافة ? تمنحها حالة "لا قيمة"، وهي ما يحتاجه حقل نموذج مفقود أو عمود NULL في قاعدة بيانات. افحصها بـ HasValue أو != null واقرأها بـ Value أو GetValueOrDefault().

ماذا يفعل ?? في C#؟

a ?? b هو معامل دمج null: يُقيَّم إلى a إن لم تكن a هي null، وإلا إلى b. تقدّم string name = input ?? "guest"; قيمة بديلة في تعبير واحد. يتسلسل (a ?? b ?? c يأخذ أول قيمة غير null) ولا تُقيَّم b إلا عند الحاجة. وتُسند ??= (C# 8) الطرف الثاني فقط حين يكون المتغيّر null.

ماذا يفعل ?. في C#؟

a?.B هو المعامل الشرطي لـ null: إن كانت a هي null يكون التعبير كله null ولا تُقيَّم B أبدًا، فلا NullReferenceException. يتسلسل (order?.Customer?.Address?.City) ويعمل مع استدعاءات الدوال (logger?.Log(...)) والأحداث (Changed?.Invoke(...)) والمفهرسات (items?[0]). اجمعه مع ?? لقيمة افتراضية: order?.Customer?.Name ?? "unknown".

ما سبب "Nullable object must have a value"؟

قراءة .Value من نوع قيمة قابل لـ null قيمته null، أو تحويله إلى النوع غير القابل لـ null ((int)maybe)، يرمي InvalidOperationException بتلك الرسالة. افحص HasValue أولًا، أو استخدم GetValueOrDefault() أو ?? fallback أو نمطًا: if (maybe is int n) { ... }.

كيف أفحص null في C#؟

x == null وx is null كلاهما يعمل؛ لا يمكن أن يتأثر is null (C# 7) بنوع يحمّل == تحميلًا زائدًا، فتفضّله قواعد شيفرة كثيرة. والنفي هو x != null، أو x is not null في C# 9. ولرفض الوسائط null استخدم ArgumentNullException.ThrowIfNull(x) (.NET 6) أو _ = x ?? throw new ArgumentNullException(nameof(x)); في الشيفرة الأقدم.

ما هي أنواع المراجع القابلة لـ null في C#؟

ميزة مترجم في C# 8، تُفعَّل بـ <Nullable>enable</Nullable> (الافتراضي في مشاريع .NET 6+ الجديدة). مع تفعيلها تعني string "لا تكون null أبدًا" وتعني string? "قد تكون null"، ويحذّر المترجم حين قد تستخدم null (CS8602) أو تُسند null إلى متغيّر غير قابل لـ null (CS8600). لا تغيّر إلا التحذيرات، لا سلوك وقت التشغيل.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن