Null Safety

🔹 Null Safety في Dart مع أمثلة 🚀

في Dart، تم تقديم Null Safety في الإصدار Dart 2.12 لحل مشكلة القيم null التي تؤدي إلى أخطاء تشغيل (Null Pointer Exceptions).
يتيح Null Safety للمطورين التأكد من أن المتغيرات لا تحتوي على null ما لم يُسمح بذلك صراحةً.


🔹 لماذا Null Safety مهم؟

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

🔹 أنواع المتغيرات في Null Safety

1️⃣ المتغيرات غير القابلة لأن تكون null (Non-nullable)

🔹 أي متغير يتم تعريفه بدون ? لا يمكن أن يكون null.

dartنسختحرير<code>void main() {
  int x = 10;  // ✅ لا يمكن أن يكون null
  // int y;    // ❌ خطأ، يجب تهيئته بقيمة
  print(x);
}
</code>
Dart

التفسير:

  • لا يمكنك ترك المتغير بدون قيمة (null غير مسموح به بشكل افتراضي).

2️⃣ المتغيرات القابلة لأن تكون null (Nullable)

🔹 لإعلان متغير يمكن أن يكون null، استخدم ?.

dartنسختحرير<code>void main() {
  int? y; // ✅ هذا المتغير يمكن أن يكون null
  print(y); // Output: null
}
</code>
Dart

التفسير:

  • int? y تعني أن y يمكن أن تكون null.

🔹 التعامل مع null بأمان

1️⃣ ! (Null Assertion Operator)

🔹 يُستخدم لإخبار Dart أن هذا المتغير لن يكون null، ولكن يجب الحذر لأنه إذا كان null، سيؤدي إلى خطأ.

dartنسختحرير<code>void main() {
  int? number;
  print(number!); // ❌ خطأ: لأن number تساوي null
}
</code>
Dart

هذا يؤدي إلى خطأ (Unhandled exception)!

✅ الحل: يجب التأكد أن المتغير ليس null قبل استخدام !.

dartنسختحريرvoid main() {
  int? number = 10;
  print(number!); // ✅ يعمل بشكل صحيح لأنه ليس null
}

2️⃣ ?. (Null-aware Operator)

🔹 تُستخدم عندما لا نريد حدوث خطأ إذا كان المتغير null.

dartنسختحرير<code>void main() {
  String? name;
  print(name?.length); // ✅ Output: null
}
</code>
Dart

التفسير:

  • إذا كانت name تساوي null، فلن تحدث مشكلة، وسيتم إرجاع null.

3️⃣ ?? (Null-coalescing Operator)

🔹 تُستخدم لتوفير قيمة افتراضية إذا كان المتغير null.

dartنسختحرير<code>void main() {
  String? username;
  print(username ?? "Guest"); // Output: Guest
}
</code>
Dart

التفسير:

  • إذا كان username null، سيتم استخدام "Guest" بدلًا منه.

4️⃣ ??= (Null-aware Assignment)

🔹 تُستخدم لتعيين قيمة للمتغير فقط إذا كان null.

dartنسختحرير<code>void main() {
  int? value;
  value ??= 5; 
  print(value); // Output: 5
}
</code>
Dart

التفسير:

  • إذا كان value null، سيتم تعيين 5 له.

🔹 Null Safety في List و Map

1️⃣ List تحتوي على null

dartنسختحرير<code>void main() {
  List<int?> numbers = [1, 2, null, 4];
  print(numbers); // Output: [1, 2, null, 4]
}
</code>
Dart

التفسير:

  • List<int?> تعني أن القائمة يمكن أن تحتوي على null.

2️⃣ List لا تحتوي على null

dartنسختحرير<code>void main() {
  List<int> numbers = [1, 2, 3, 4];
  print(numbers); // Output: [1, 2, 3, 4]
}
</code>
Dart

التفسير:

  • List<int> تعني أن القائمة يجب ألا تحتوي على null.

3️⃣ Map تحتوي على null

dartنسختحرير<code>void main() {
  Map<String, int?> ages = {
    "Ali": 30,
    "Omar": null,
  };
  print(ages["Omar"] ?? "Age not available"); // Output: Age not available
}
</code>
Dart

التفسير:

  • Map<String, int?> تعني أن القيم في الـ Map يمكن أن تكون null.

🔹 Null Safety مع الدوال

1️⃣ تحديد أن القيم المرجعة غير قابلة لأن تكون null

dartنسختحرير<code>int add(int a, int b) {
  return a + b;
}

void main() {
  print(add(5, 10)); // Output: 15
}
</code>
Dart

التفسير:

  • int add(int a, int b) تعني أن الدالة لن تُرجع null.

2️⃣ دالة قد تُرجع null

dartنسختحرير<code>int? findEvenNumber(List<int> numbers) {
  for (var num in numbers) {
    if (num.isEven) return num;
  }
  return null;
}

void main() {
  print(findEvenNumber([1, 3, 5])); // Output: null
  print(findEvenNumber([1, 2, 3])); // Output: 2
}
</code>
Dart

التفسير:

  • int? تعني أن الدالة قد تُرجع null.

🔹 Null Safety في late

🔹 late تسمح بتأجيل تهيئة المتغير، لكنها تضمن عدم كونه null عند استخدامه.

dartنسختحرير<code>class User {
  late String name;

  void setName(String newName) {
    name = newName;
  }
}

void main() {
  User user = User();
  user.setName("Ali");
  print(user.name); // Output: Ali
}
</code>
Dart

التفسير:

  • late تسمح بتهيئة name لاحقًا، ولكنها لا تسمح بأن تكون null عند استخدامها.

🔹 الفرق بين ! و late

الميزة! (Null Assertion)late
متى يُستخدم؟عندما نكون متأكدين أن المتغير لن يكون null.عندما نريد تأجيل التهيئة ولكن نضمن أنه لن يكون null عند الاستخدام.
هل يمكن أن يسبب خطأ؟نعم، إذا كان null سيسبب Exception.لا يسبب خطأ طالما تم تعيين قيمة قبل الاستخدام.

🎯 خلاصة Null Safety

استخدم ? إذا كنت تريد السماح بـ null
استخدم ! فقط إذا كنت متأكدًا أن المتغير لن يكون null
استخدم ?? أو ??= لتوفير قيم افتراضية
استخدم ?. إذا كنت تريد تجنب Null Pointer Exception
استخدم late إذا كنت تريد تأجيل التهيئة ولكن تضمن عدم وجود null عند الاستخدام