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.
- 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
| Syntax | Meaning | Can Be Null | Compiler Warns | Use When |
|---|---|---|---|---|
string name | Non-nullable | Never | If assigned null | Data required |
string? name | Nullable reference | Yes | If not checked | Data optional |
int age | Non-nullable value | Never | N/A | Age required |
int? age | Nullable value type | Yes | If not checked | Age optional |
student?.Name | Null-conditional | Safe, returns null | No | Safe property access |
name ?? "Unknown" | Null-coalescing | Provides default | No | Default fallback |
name ??= "Unknown" | Assign if null | Assigns if null | No | Initialize if null |
student!.Name | Null-forgiving | Bypass check | No | Force when you're sure |
student is null | Null check | Pattern match | No | Check explicitly |
student is not null | Non-null check | Pattern match | No | Guard 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
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.
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
- Enable Nullable in project - Compile-time safety
- Mark optional fields with
?-string? emailnotstring email - Use
is not nullfor checks - Clear pattern matching - Use
??for defaults -name ?? "Unknown" - Use
?.for safe access -student?.Emailreturns null if student null - Avoid
!(null-forgiving) - Use only when 100% certain - Return
T?from methods that might fail -Student? FindStudent(id) - Use
ArgumentNullException.ThrowIfNull- Guard clause for parameters - Trust compiler warnings - They prevent NullReferenceException
- Document why field can be null - Comments explain optional fields
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.
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.
?. (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.
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.
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.
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 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.