Skip to main content

29. Nullable Types in C#

Level: Intermediate
Goal: Learn nullable types - allow data to be "nothing" safely, catch null bugs before they crash.

A variable can be empty (null). Nullable types let you represent "no value" and check before using.

Example:

Student? student = GetStudent(); // May be null
if (student != null)
{
Console.WriteLine(student.Name); // Safe
}

Without nullable, the program crashes if student is null.

ℹ️ What You'll Learn
  • Nullable reference types (string?, Student?)
  • Nullable value types (int?, double?)
  • Null-conditional operator (?.
  • Null-coalescing operator (??)
  • Null-forgiving operator (!)
  • Pattern matching for null checks
  • Best practices for null safety
  • Compile-time vs runtime null checks

Quick Definitions

  • Nullable - Variable can hold value or null (nothing)
  • Nullable reference type - string?, Student?, int? (must check before use)
  • Nullable value type - int?, double?, bool? (wraps primitive in nullable)
  • Null-conditional operator - ?. (safe: returns null if operand is null)
  • Null-coalescing operator - ?? (provides default: x ?? "default")
  • Null-coalescing assignment - ??= (assign only if currently null)
  • Null-forgiving operator - ! (force: tell compiler "I know it's not null")
  • Pattern matching - is null, is not null, x is { Name: "S" })
  • Compile-time check - Compiler warns about nullable misuse before runtime
  • NullReferenceException - Runtime crash from accessing null object

Nullable Types Reference

SyntaxMeaningCan Be NullCompiler WarnsUse When
string nameNon-nullableNeverIf assigned nullData required
string? nameNullable referenceYesIf not checkedData optional
int ageNon-nullable valueNeverN/AAge required
int? ageNullable value typeYesIf not checkedAge optional
student?.NameNull-conditionalSafe, returns nullNoSafe property access
name ?? "Unknown"Null-coalescingProvides defaultNoDefault fallback
name ??= "Unknown"Assign if nullAssigns if nullNoInitialize if null
student!.NameNull-forgivingBypass checkNoForce when you're sure
student is nullNull checkPattern matchNoCheck explicitly
student is not nullNon-null checkPattern matchNoGuard clause

Enable Nullable (Already on in .NET 10)

<!-- .csproj -->
<Nullable>enable</Nullable>

Nullable vs Non-Nullable

// Non-nullable - compiler warns if possibly null
string name = "Sahasra"; // cannot be null
string email = GetEmail(); // if GetEmail() might return null ? warning

// Nullable - explicitly opt-in to null
string? middleName = null; // ? allowed
string? guardianPhone = null; // ? allowed

School Management - Student with Nullable

public class Student
{
// Required - never null
public required int Id { get; set; }
public required string Name { get; set; }
public required string RollNumber { get; set; }
public required string ClassName { get; set; }

// Optional - explicitly nullable
public string? MiddleName { get; set; }
public string? GuardianPhone { get; set; }
public string? Email { get; set; }
public string? PhotoUrl { get; set; }

public double Percentage { get; set; }
public bool IsActive { get; set; } = true;
}

Null-Safe Operators

Student? student = FindStudent(999); // might not exist

// ?. - null-conditional - returns null instead of throwing
string? name = student?.Name;
string? upper = student?.Name?.ToUpper();
int? nameLen = student?.Name?.Length;

// ?? - null-coalescing - default value
string display = student?.Name ?? "Student Not Found";
string email = student?.Email ?? "No Email";

// ??= - assign if null
student ??= new Student { Id=0, Name="Unknown", RollNumber="N/A", ClassName="N/A" };

// ! - null-forgiving (use sparingly - bypasses null check)
// Only use when you KNOW it's not null
string definitelyNotNull = student!.Name;

Null Checks - Best Practices

static void ProcessStudent(Student? student)
{
// [X] Bad - throws NullReferenceException if null
Console.WriteLine(student.Name);

// Option 1 - explicit null check
if (student is null)
{
Console.WriteLine("No student.");
return;
}
Console.WriteLine(student.Name); // compiler knows not null here

// Option 2 - pattern matching
if (student is not null)
Console.WriteLine(student.Name);

// Option 3 - throw if null (guard clause)
ArgumentNullException.ThrowIfNull(student);
Console.WriteLine(student.Name);
}

// Return null safely from service
static Student? FindStudentByRoll(List<Student> students, string rollNumber)
=> students.FirstOrDefault(s => s.RollNumber == rollNumber);

// Caller handles null
var found = FindStudentByRoll(students, "NCA-2024-0001");
if (found is not null)
Console.WriteLine($"Found: {found.Name}");
else
Console.WriteLine("Student not found.");

Nullable Value Types

💻 Try It — Console App
💡 Paste into Program.cs and press F5⌥ GitHub
// Value types (int, double, bool) cannot be null normally
int marks = 85;
int? optional = null; // int? = Nullable<int>

// Check
if (optional.HasValue)
Console.WriteLine(optional.Value);

int safe = optional.GetValueOrDefault(0); // 0 if null
int or = optional ?? 0; // same

// Real usage - exam marks not yet entered
public class ExamMarks
{
public int StudentId { get; set; }
public string Subject { get; set; } = "";
public int? Marks { get; set; } // null = not yet marked
public bool IsAbsent { get; set; }

public string GetStatus()
{
if (IsAbsent) return "Absent";
if (!Marks.HasValue) return "Not Marked";
return Marks.Value >= 35 ? "Pass" : "Fail";
}
}

When You'll Use This in SMS

Nullable prevents crashes in SMS:

// Student might not exist
Student? student = GetStudent(rollNumber);
if (student is not null)
Console.WriteLine(student.Name); // Safe

// GuardianPhone might be missing
string? guardianPhone = student?.GuardianPhone;
string phoneToShow = guardianPhone ?? "N/A"; // Default if null

// Optional fee discount
decimal? discount = student?.FeeDiscount;
decimal finalFee = totalFee - (discount ?? 0); // Use 0 if null

Real impact: Without nullable = crash if data missing. With nullable = safe default handling.


ℹ️ Video Tutorial

Nullable types explained: nullable references, value types, ??., ?., !, pattern matching. Video coming soon. Subscribe to NexCoding YouTube for updates.


Common Mistakes

? Not checking null before using (NullReferenceException):

Student? student = FindStudent(999);
Console.WriteLine(student.Name); // Crash if student is null!

? Check before using:

Student? student = FindStudent(999);
if (student is not null)
Console.WriteLine(student.Name); // Safe

? Confusing ? on reference with ? on value type:

int? marks = null; // Nullable INT (value type)
string? name = null; // Nullable STRING (reference type)
// Both allow null, but int can't be null normally, string can

? Understand difference:

int x = null; // ERROR! int cannot be null
int? x = null; // OK, nullable int
string s = null; // WARNING! compiler warns
string? s = null; // OK, explicitly nullable

? Using ! to bypass null checks (dangerous):

string? name = GetName();
string upper = name!.ToUpper(); // If GetName() returns null, CRASH!

? Check first:

string? name = GetName();
string upper = name?.ToUpper() ?? "UNKNOWN"; // Safe

? Not enabling Nullable in project:

<!-- Missing in .csproj - no compile-time null warnings -->

? Enable for compile-time safety:

<Nullable>enable</Nullable>

Best Practices

  1. Enable Nullable in project - Compile-time safety
  2. Mark optional fields with ? - string? email not string email
  3. Use is not null for checks - Clear pattern matching
  4. Use ?? for defaults - name ?? "Unknown"
  5. Use ?. for safe access - student?.Email returns null if student null
  6. Avoid ! (null-forgiving) - Use only when 100% certain
  7. Return T? from methods that might fail - Student? FindStudent(id)
  8. Use ArgumentNullException.ThrowIfNull - Guard clause for parameters
  9. Trust compiler warnings - They prevent NullReferenceException
  10. Document why field can be null - Comments explain optional fields

🎯 Q1: Why do nullable types exist and what problem do they solve?

Before C# 8: Everything could be null, no warning. NullReferenceException at runtime (crash).

C# 8+: Mark what CAN be null (string?), compiler warns if you use it without checking.

string name = GetName(); // Compiler warns: GetName() might return null
string? name = GetName(); // OK, explicitly nullable
Console.WriteLine(name); // Compiler warns: might be null
Console.WriteLine(name!); // Force (only if sure)
Console.WriteLine(name ?? "Unknown"); // Safe, provides default

Compile-time safety prevents crashes.

🎯 Q2: What's the difference between string? and int??

string? - Reference type, can be null (already can be). Makes EXPLICIT that it can be null.

int? - Value type, normally CAN'T be null. int? (Nullable<int>) lets it be null.

string name = null; // WARNING - strings can be null, but you said required
string? name = null; // OK - explicitly nullable
string name = "Sahasra"; // OK - non-null

int age = null; // ERROR! ints can't be null
int? age = null; // OK - nullable int
int age = 25; // OK - non-null

int? enables null for value type. string? documents that reference type is optional.

🎯 Q3: What are the ?. , ??, and ??= operators?

?. (null-conditional): Access property safely, returns null if object null.

?? (null-coalescing): Provide default value if left side null.

??= (assign if null): Assign value only if currently null.

Student? student = FindStudent(999);

string name = student?.Name; // null if student null
string display = student?.Name ?? "Unknown"; // Use "Unknown" if null
student ??= new Student { ... }; // Create if was null

?. safe access, ?? default fallback, ??= lazy initialization.

🎯 Q4: When should I use null-forgiving (!) operator?

Use ! ONLY when 100% certain value is not null. Bypasses compiler safety check.

string? name = FindStudent(1)?.Name; // Compiler warns: might be null
string upper = name!.ToUpper(); // You promise it's not null
// If you're wrong: NullReferenceException!

Better: Avoid !. Use ?? instead:

string upper = (name ?? "Unknown").ToUpper(); // Safe, no promise needed

! only for cases where logic guarantees non-null but compiler can't prove it.

🎯 Q5: How do I check for null safely?

Three ways, all good:

Student? s = FindStudent(1);

// Way 1 - explicit null check
if (s == null) return;

// Way 2 - pattern matching (modern, preferred)
if (s is null) return;
if (s is not null) Console.WriteLine(s.Name);

// Way 3 - null-conditional + coalescing
var name = s?.Name ?? "Unknown";

Pattern matching (is null, is not null) is modern C#, clearest intent.

🎯 Q6: Why enable nullable in the project?

Without <Nullable>enable</Nullable>: No compiler warnings, NullReferenceException at runtime.

With <Nullable>enable</Nullable>: Compiler warns before runtime, catches bugs early.

// Without nullable enabled - no warning!
string? name = GetName();
Console.WriteLine(name.Length); // Might crash, compiler silent

// With nullable enabled - compiler warns!
string? name = GetName();
Console.WriteLine(name.Length); // WARNING: dereference of possibly null reference
Console.WriteLine(name?.Length); // OK - safe access

Enable Nullable = prevent crashes = save debugging time.

🤖Use AI to Learn Faster

Use ChatGPT, Claude, or Copilot to go deeper on C# nullable reference types and null safety. Try these prompts:

  • "Why does NullReferenceException happen in C# and how do nullable types prevent it?"
  • "What is the difference between int? and string? in C#?"
  • "Explain the ?., ??, and ??= operators in C# with real examples"
  • "When should I use ArgumentNullException.ThrowIfNull vs if (x is null)?"

💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.

Next Article

IDisposable ->

nexcoding.in