C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
228: Enabling Nullable Context
I can't tell you how many times I've had to debug a production crash that looked exactly like this. You're looking at a piece of code that seems perfectly logical, but it's hiding a landmine that only explodes when a specific user enters a specific set of data.
public class UserProfile
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
}
public class ProfilePrinter
{
public void PrintFullName(UserProfile profile)
{
// This looks fine, right?
Console.WriteLine($"User: {profile.FirstName} {profile.MiddleName.ToUpper()} {profile.LastName}");
}
}
The problem here is the MiddleName. Not everyone has one. If a UserProfile is passed in where MiddleName is null, the call to ToUpper() will throw a NullReferenceException. The worst part? The compiler didn't give you a single warning. It just let you walk right into the wall.
The Invisible Danger of Default Reference Types
By default, in older versions of C# (or in projects where the nullable context is disabled), all reference types are "nullable." This means string can be a string, or it can be null, and the compiler doesn't care which one it is. It assumes you know what you're doing. But as we've seen, we aren't always right.
When the nullable context is disabled, the compiler treats string and string? as exactly the same thing. There is no distinction. You are essentially flying without a radar.
Turning on the Safety Rails
To fix this, we need to enable the Nullable Reference Types feature. This isn't a change to your code logic, but a change to how the compiler views your types. You do this in your project file (the .csproj file).
Open your .csproj and add the <Nullable>enable</Nullable> tag inside the PropertyGroup:
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
The moment you save this file, your IDE is going to light up with warnings. Don't panic—this is actually the feature working. The compiler is now telling you, "Hey, you said MiddleName is a string, but you never assigned it a value, so it's going to be null by default. That's a problem."
Fixing the Warnings with Intent
Now that the context is enabled, we have to be explicit about our intent. If a value can be null, we mark it with a ?. If it cannot be null, we leave it as is and ensure it's initialized.
public class UserProfile
{
// These are required. The compiler will warn if they aren't set.
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
// This is optional. The '?' tells the compiler "I know this might be null."
public string? MiddleName { get; set; }
}
public class ProfilePrinter
{
public void PrintFullName(UserProfile profile)
{
// The compiler now warns us that MiddleName might be null here!
// We fix it using the null-conditional operator.
string middle = profile.MiddleName?.ToUpper() ?? "";
Console.WriteLine($"User: {profile.FirstName} {middle} {profile.LastName}");
}
}
By enabling the nullable context, we've shifted the discovery of the bug from Runtime (where the user sees a crash) to Compile-time (where you see a yellow squiggly line). I highly recommend enabling this on every new project you start; it forces you to think about the "null case" before you even hit the run button.
📋 Practical Task
Hardening the Customer Address Validator
You have been handed a Customer class used in a shipping system. Currently, the project has <Nullable>enable</Nullable> turned on, but the previous developer ignored all the compiler warnings, leaving the code fragile.
Your Task: Fix the following code so that there are zero compiler warnings and no possibility of a NullReferenceException during the GetShippingLabel call.
public class Customer
{
public string Name { get; set; }
public Address ShippingAddress { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string? ZipCode { get; set; } // ZipCode is optional
}
public class ShippingService
{
public string GetShippingLabel(Customer customer)
{
// WARNING: customer.ShippingAddress might be null
// WARNING: customer.ShippingAddress.Street might be null
return $"Ship to: {customer.Name} at {customer.ShippingAddress.Street}, {customer.ShippingAddress.City}";
}
}
Requirements:
- Ensure
Name,Street, andCityare treated as non-nullable and initialized (usestring.Emptyor a constructor). - Keep
ZipCodeas optional. - Update
GetShippingLabelto safely handle the possibility that theShippingAddressobject itself might be null, returning "No Address Provided" if it is.
There are no comments for now.