Hello! Welcome to your fifth lesson on mastering C#.
In our previous lesson, we explored how to use enum types as bit fields with the [Flags] attribute. We reinforced the core concept that enums, like all structs, are value types. A crucial characteristic of value types is that they cannot be null; an int variable, for example, always holds an integer, defaulting to 0 if not explicitly initialized.
However, in real-world applications, especially those interacting with databases like your ASP.NET project, you frequently need to represent a value that is "missing" or "not yet set." A database column for a BirthDate or ProductPrice might permit NULL entries. How do we model this in C# when DateTime and decimal are value types?
This lesson directly answers that question and addresses the learning outcome: Use nullable value types (Nullable
1. The Problem: When a Value is "Missing"
In many programming scenarios, there's a meaningful difference between a value being present (even if it's a default like 0) and a value being absent.
To explore this, let's watch a short video that uses a practical database example to illustrate why this distinction is so important.
C# Nullables - How to CREATE and when to USE them
The video 'C# Nullables - How to CREATE and when to USE them' by tutorialsEU - C# starts by explaining the difference between a price being zero and a price being null, a very common scenario in application development.
Please watch the first 2 minutes of the video (00:00 - 02:02). The speaker will demonstrate why primitive types like int and bool can't be assigned null and why this poses a problem.
As the video shows, attempting to assign null to a standard value type variable results in a compile-time error. C# enforces that a value type must always hold a valid value of its type.
int pageViews = 0; // Valid, pageViews is zero
bool isComplete = false; // Valid, isComplete is false
// The following lines will NOT compile
// int? pageViews = null; // This is a different type, we'll get to it
int pageViews = null; // Error: Cannot convert null to 'int' because it is a non-nullable value type.
bool isComplete = null; // Error: Cannot convert null to 'bool' ...
2. The Solution: Nullable<T> and the ? Shorthand
C# provides an elegant solution through a generic struct called System.Nullable<T>. This struct wraps a value type T and adds the ability to be null.
While you can write Nullable<int>, C# provides a much more common and readable shorthand syntax: a question mark ? after the type name.
Nullable<int>is the same asint?Nullable<DateTime>is the same asDateTime?Nullable<bool>is the same asbool?
Let's see how this is used in practice.
C# Nullables - How to CREATE and when to USE them
Continuing with the same video, let's see how Nullable<T> and the ? syntax solve the problem.
Now watch from 02:02 to 05:26. This segment will introduce both the full Nullable<T> syntax and the ? shorthand, extending the concept to bool types and reiterating the database use case.
With this syntax, you can now represent that missing value:
// Now this is valid
int? pageViews = null;
DateTime? lastLoginDate = null;
if (pageViews.HasValue)
{
Console.WriteLine($"Page views: {pageViews.Value}");
}
else
{
Console.WriteLine("Page view count is not available.");
}
3. "Under the Hood": How Does Nullable<T> Work?
This is where things get interesting and address your goal of understanding the internals. You might assume Nullable<T> is a reference type, but it is actually a struct. So how can a value type be null?
The Nullable<T> struct is surprisingly simple. It contains two fields:
- A
boolfield namedhasValue. - A field of type
Tnamedvalue.
When you assign a non-null value to a int? variable, the hasValue flag is set to true, and the value field stores the integer. When you assign null to it, the hasValue flag is set to false.
Inside C#: Stack & Heap, Value Types, Boxing, stackalloc + More
The video 'Inside C#: Stack & Heap, Value Types, Boxing, stackalloc + More' has a concise segment that explains this internal mechanism.
Watch from 33:00 to 34:19. This part clearly explains that a Nullable<T> is essentially the value type plus a boolean flag to track the null state.
An Important Distinction: Compiler Magic
Since Nullable<T> is a struct, you might think you could build your own. However, if you try, you'll find you can't assign null to it.
// You cannot do this with a custom struct, even if it mimics Nullable<T>
MyNullable<int> myNum = null; // This will cause a compile error!
The ability to write int? myNum = null; is syntactic sugar provided by the C# compiler and supported by the .NET Common Language Runtime (CLR). The compiler translates the null assignment into an instruction that creates a Nullable<T> instance with its hasValue flag set to false.
For a deeper confirmation of this special language feature, it's worth seeing how the community discusses it.
c# - Nullable<T> implementation - Stack Overflow
This Stack Overflow thread, 'Nullable implementation', directly addresses why a custom-built nullable type fails. The answers confirm that it's a special feature of the language and CLR.
Read the top two answers (by MarcinJuraszek and Dennis). They explain that assigning null is a language feature and that Nullable<T> gets special treatment from the CLR, which is why you can't replicate it yourself.
4. Practical Usage: Safely Working with Nullable Types
Because a nullable variable might not have a value, you must be careful when accessing it. Nullable<T> provides members to help you do this safely.
Please read the following article, which provides clear examples for the three most important members you'll use.
C# Nullable types; what are they and why do we need them?
The article 'C# Nullable types; what are they and why do we need them?' offers a great practical guide to interacting with nullable types.
Read the section that begins 'Now let’s take a closer look at the members of a nullable type.' down to 'Now let me show you something else about Nullables.'. Focus on the definitions and examples for HasValue, Value, and GetValueOrDefault().
Let's summarize the key members:
HasValue(Property): Aboolthat returnstrueif the variable holds a value andfalseif it isnull. This is your primary tool for checking the state.Value(Property): Returns the underlying value. Warning: Accessing this property whenHasValueisfalsewill throw aSystem.InvalidOperationException. You should always checkHasValuefirst.

GetValueOrDefault()(Method): Safely returns the underlying value if one exists, or the default value for the typeTif it isnull(e.g.,0forint,falseforbool).
Performance Note on Null Checks
You can check if a nullable type has a value in a few ways:
int? orderId = 123;
if (orderId.HasValue) { /* ... */ }
if (orderId != null) { /* ... */ }
if (orderId is not null) { /* ... */ } // Modern C#
For most applications, the difference is negligible, and is not null is often preferred for readability. However, since you're interested in performance, it's good to know that direct property access is the fastest.
5. Idiomatic C#: The Null-Coalescing Operator (??)
C# provides a fantastic shortcut for the common pattern of "use this value if it's not null; otherwise, use a fallback value." This is the null-coalescing operator (??).
It's far more concise than an if/else block or using GetValueOrDefault().
C# Nullable types; what are they and why do we need them?
Let's return to the '99coding.club' article to see a clear comparison.
Read the section titled 'Null Coalescing operator'. It shows how a multi-line if-else block can be replaced by a single, expressive line of code using ??.
Here is the comparison:
int? nullableId = null;
// Using if-else
int id1;
if (nullableId.HasValue)
{
id1 = nullableId.Value;
}
else
{
id1 = -1; // Default/fallback value
}
// Using GetValueOrDefault
// Note: GetValueOrDefault() without a parameter would return 0.
// To specify a different default, you can pass it as an argument.
int id2 = nullableId.GetValueOrDefault(-1);
// Using the null-coalescing operator
int id3 = nullableId ?? -1; // "If nullableId is not null, use its value; otherwise, use -1."
// All three result in id1, id2, and id3 being -1.
The ?? operator is a cornerstone of modern, concise C# code.
6. Conversions Between Nullable and Non-Nullable Types
Finally, it's important to understand how C# handles assignments between T and T?.
-
TtoT?(Implicit): You can always assign a non-nullable value to a nullable one. This conversion is safe and happens automatically (implicitly).int myValue = 10; int? nullableValue = myValue; // Works perfectly -
T?toT(Explicit): You cannot directly assign a nullable value to a non-nullable one because the nullable value might benull. The compiler requires you to be explicit about how to handle this potentialnullcase.
C# Nullable types; what are they and why do we need them?
This final reading from '99coding.club' demonstrates these conversion rules with clear code examples.
Read the section starting with 'Now let me show you something else about Nullables.' down to 'Null Coalescing operator'. This part shows the compiler error when assigning a nullable to a non-nullable type and the correct way to handle it.
To perform the conversion, you must use a cast (which will throw an exception if the value is null) or, more safely, use GetValueOrDefault() or the ?? operator.
int? nullableValue = 10;
int myValue;
// myValue = nullableValue; // Compile Error!
// Safe ways to convert:
myValue = nullableValue.Value; // Safe only if you've checked HasValue
myValue = nullableValue.GetValueOrDefault(); // Safe, uses 0 if null
myValue = nullableValue ?? 0; // Safe, uses 0 if null
myValue = (int)nullableValue; // Explicit cast. Throws InvalidOperationException if null.
Conclusion
This lesson tackled a fundamental aspect of C# programming: bridging the gap between value types and the real-world need for null.
Key Takeaways:
- Value types (
int,bool,struct, etc.) cannot benullby default. - To allow
null, use theNullable<T>struct, or more commonly, the?shorthand (e.g.,int?). - Under the hood,
Nullable<T>is a struct containing the value and abool HasValueflag. The ability to assignnullis a special feature of the C# compiler and CLR. - Always check for
nullbefore accessing the.Valueproperty to avoid anInvalidOperationException. UseHasValue,!= null, oris not null. - Use
GetValueOrDefault()or the null-coalescing operator (??) for safely retrieving the value or providing a default. - Conversion from
TtoT?is implicit, but conversion fromT?toTis explicit and requires you to handle the potentialnullcase.
Preview of the Next Lesson:
We saw that the is keyword can be used for null checks (is not null). In our next lesson, "Perform type checking and casting using the is and as operators," we will explore the full power of these operators. You'll learn how to check an object's type at runtime and safely cast it, which is essential for building flexible and robust systems.