Nullable Types and Pattern Matching
Nullable Value Types
Value types cannot be null by default. The Nullable<T> struct (shorthand T?) allows value types to carry null semantics.
int? a = 42;
int? b = null;
bool? flag = null;
double? price = 9.99;
Console.WriteLine(a.HasValue);
Console.WriteLine(b.HasValue);
Console.WriteLine(flag.GetValueOrDefault());
Console.WriteLine(price.GetValueOrDefault(0.0));
True
False
False
9.99
Example
int? score = null;
Console.WriteLine(score.HasValue);
Console.WriteLine(score.GetValueOrDefault());
Console.WriteLine(score.GetValueOrDefault(60));
score = 88;
Console.WriteLine(score.Value);
False
0
60
88
Null Coalescing and Null Conditional Operators
?? provides a default value, ?. safely accesses members, and ! tells the compiler "this is not null here."
int? x = null;
int y = x ?? 0;
string? name = null;
int? len = name?.Length;
string s = null!;
Console.WriteLine(s is null);
True
Example
string? input = null;
string display = input ?? "(empty)";
Console.WriteLine(display);
int? length = input?.Length;
Console.WriteLine(length ?? -1);
string? city = "Beijing";
Console.WriteLine(city?.Length ?? 0);
(empty)
-1
7
Nullable Reference Types
Starting with C# 8, you can use #nullable enable to turn on nullable reference type warnings. string? allows null, string does not.
#nullable enable
string? maybeNull = null;
string notNull = "hello";
maybeNull = null;
notNull = null!;
Example
#nullable enable
string Greet(string? name)
{
return name is null ? "Hello, stranger" : $"Hello, {name}";
}
Console.WriteLine(Greet(null));
Console.WriteLine(Greet("Alice"));
Hello, stranger
Hello, Alice
Null Check Strategies
| Strategy | Usage | Scenario |
|---|---|---|
| Explicit check | if (x is null) |
Branch logic needed |
| Null coalescing | x ?? defaultValue |
Providing a default value |
| Null conditional | x?.Member |
Safe member access |
Example
string? GetName() => null;
string? name = GetName();
if (name is null)
{
Console.WriteLine("Name is null");
}
string upper = name?.ToUpper() ?? "DEFAULT";
Console.WriteLine(upper);
Name is null
DEFAULT
Introduction to Pattern Matching
Pattern matching combines type checking and variable extraction into one, commonly used with is and switch.
is Pattern
object obj = 42;
if (obj is int i)
{
Console.WriteLine($"Is integer: {i}");
}
Is integer: 42
Example
object value = "hello";
if (value is int n)
{
Console.WriteLine(n);
}
else if (value is string s)
{
Console.WriteLine($"String length: {s.Length}");
}
String length: 5
Type Pattern and switch
Branch by type in switch and extract variables.
Example
string Describe(object obj)
{
return obj switch
{
int i => $"Integer {i}",
double d => $"Double {d}",
string s => $"String \"{s}\"",
bool b => $"Boolean {b}",
null => "null",
_ => "Unknown type"
};
}
Console.WriteLine(Describe(10));
Console.WriteLine(Describe(3.14));
Console.WriteLine(Describe("hi"));
Console.WriteLine(Describe(true));
Integer 10
Double 3.14
String "hi"
Boolean True
Property Pattern
Property patterns match by object properties without manually extracting values for comparison.
Example
var person = new { Name = "Alice", Age = 20 };
if (person is { Age: > 18 })
{
Console.WriteLine("Adult");
}
string Category(object p) => p switch
{
{ Age: < 12 } => "Child",
{ Age: >= 12 and < 18 } => "Teenager",
{ Age: >= 18 } => "Adult",
_ => "Unknown"
};
Console.WriteLine(Category(person));
Adult
Adult
Record Type
C# 9 introduced record, which provides value-based equality comparison and immutability by default.
record Person(string Name, int Age);
Example
record Person(string Name, int Age);
var p1 = new Person("Alice", 18);
var p2 = new Person("Alice", 18);
Console.WriteLine(p1 == p2);
Console.WriteLine(ReferenceEquals(p1, p2));
Console.WriteLine(p1);
True
False
Person { Name = Alice, Age = 18 }
with Expression
with creates a copy of an existing record with some properties modified, leaving the original instance unchanged.
Example
record Student(string Name, int Age, string Grade);
var s1 = new Student("Bob", 20, "A");
var s2 = s1 with { Age = 21, Grade = "A+" };
Console.WriteLine(s1);
Console.WriteLine(s2);
Student { Name = Bob, Age = 20, Grade = A }
Student { Name = Bob, Age = 21, Grade = A+ }
Tuple
Tuples combine multiple values into a lightweight structure, supporting named elements and deconstruction.
Example
(int Id, string Name) person = (1, "Charlie");
Console.WriteLine(person.Id);
Console.WriteLine(person.Name);
var coords = (X: 3.0, Y: 4.0);
double distance = Math.Sqrt(coords.X * coords.X + coords.Y * coords.Y);
Console.WriteLine(distance);
(int x, int y) = (10, 20);
Console.WriteLine($"x={x}, y={y}");
1
Charlie
5
x=10, y=20
Tuple Deconstruction and Pattern Matching
Combining tuples with pattern matching elegantly handles multi-condition branches.
Example
string Classify(int score, bool hasBonus) => (score, hasBonus) switch
{
(>= 90, true) => "Excellent+Bonus",
(>= 90, false) => "Excellent",
(>= 60, true) => "Pass+Bonus",
(>= 60, false) => "Pass",
_ => "Fail"
};
Console.WriteLine(Classify(95, true));
Console.WriteLine(Classify(75, false));
Console.WriteLine(Classify(50, true));
Excellent+Bonus
Pass
Fail
❓ FAQ
int? and int be used in arithmetic directly?.Value or ?? before operating.string? and string?string? allows assigning null, while string will produce a compiler warning when assigned null with nullable enabled.📖 Summary
T?isNullable<T>, allowing value types to be nullHasValue/Value/GetValueOrDefault()access nullable values??,?.,!handle null scenarios#nullable enableturns on nullable reference type checkingispattern, type pattern, and property pattern simplify type checkingswitchexpressions with pattern matching concisely express multi-branch logicrecordprovides value equality and immutabilitywithcreates record copies with some properties modified- Tuples lightweightly combine multiple values, supporting naming and deconstruction
📝 Exercises
- Declare
double? price = null;, useGetValueOrDefault(99.9)to get the default value and output it - Write a method that accepts a
string?parameter, uses?.and??to return the string length or-1 - Use the
ispattern to check ifobject obj = 3.14;isdouble d, and outputd * 2 - Define
record Book(string Title, double Price);, create an instance, and usewithto modify the price - Use a tuple switch expression to check
(int month, int day)for whether it is "New Year's Day" or "National Day"



