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
17: Nullable Reference Types
If you've just turned on Nullable Reference Types in your project settings, you might be under the impression that you've just installed a "Null-Proof Shield" around your code. I see this all the time with developers moving from older versions of C#: the belief that marking a type as non-nullable actually prevents it from being null at runtime.
The myth that non-nullable means "impossible to be null"
Let's get this straight right away: string Name is not fundamentally different from string? Name when your code is actually running. The "non-nullable" part is a contract for the compiler, not a physical constraint on the memory.
Look at this snippet. I'm telling the compiler that Address can never be null, but I'm going to force it to be null anyway:
public class UserProfile
{
public string Address { get; set; } = "Unknown";
}
// ... somewhere else in the code ...
UserProfile profile = new UserProfile();
// This is a dirty trick, but it shows the point
profile.Address = null!;
Console.WriteLine(profile.Address.Length); // CRASH! NullReferenceException
The ! (null-forgiving operator) tells the compiler, "Shut up, I know what I'm doing." Because the compiler trusts you, it stops warning you. But the runtime doesn't care about your contract. It just sees a null reference and throws the same NullReferenceException we've been fighting since the 90s. The "shield" is just a set of warnings during development.
Turning warnings into a safety contract
The real power of Nullable Reference Types isn't that they stop nulls, but that they force you to be honest about where nulls might come from. Instead of guessing if a method might return null, the method signature tells you explicitly.
If I change a property to string?, I'm admitting: "This value might be missing." Once I do that, the compiler becomes a very helpful nag. It won't let me use that variable without checking it first.
public class Order { public int OrderId { get; set; } public string? DiscountCode { get; set; } // Honest: not every order has a code } var myOrder = new Order(); // Console.WriteLine(myOrder.DiscountCode.Length); // The compiler flags this as a warning! if (myOrder.DiscountCode != null) { Console.WriteLine(myOrder.DiscountCode.Length); // Now the compiler is happy. }I personally love this because it moves the "debugging" phase from runtime (where the app crashes for the user) to compile-time (where I just have to fix a yellow squiggly line in my IDE).
Dealing with the "I know more than the compiler" moments
You'll inevitably run into situations where the compiler thinks something could be null, but you know—based on the logic of your app—that it absolutely isn't. This happens a lot with unit tests or legacy APIs.
- The Null-Forgiving Operator (
!): As shown earlier, appending!tells the compiler to ignore the null warning. Use this sparingly. If you find yourself using it everywhere, you're probably just lying to yourself to make the warnings go away. - The Null-Coalescing Operator (
??): Instead of forcing a non-null type, just provide a fallback.string displayCode = myOrder.DiscountCode ?? "NO_CODE";is infinitely safer than using!.
The goal here isn't to eliminate null entirely—that's nearly impossible in C#—but to make the nulls explicit. When you see a ?, you know you need a guard clause. When you don't, you can breathe a little easier.
📋 Practical Task
Fixing the Broken Customer Database Loader
You've inherited a piece of code that loads customer data from a mock database. The project has Nullable Reference Types enabled, but the previous developer used the ! operator to silence all the warnings rather than actually handling the nulls. This is causing random crashes in production.
Your Task: Refactor the following code to remove the dangerous null-forgiving operators and implement proper null handling using either if checks or the null-coalescing operator (??).
public class Customer
{
public string Name { get; set; } = string.Empty;
public string? Email { get; set; } // Email is optional
}
public class CustomerService
{
public void PrintCustomerSummary(Customer? customer)
{
// BUG: This will crash if customer is null because of the '!' operator
Console.WriteLine($"Processing: {customer!.Name}");
// BUG: This will crash if Email is null because of the '!' operator
Console.WriteLine($"Email: {customer!.Email!.ToLower()}");
}
}
// Test Case
var service = new CustomerService();
service.PrintCustomerSummary(null); // This currently crashes
Requirements:
- Ensure
PrintCustomerSummarycan handle anullcustomer without crashing. - Ensure the
Emailis handled gracefully if it is null (e.g., print "No email provided"). - Remove all instances of the
!operator.
There are no comments for now.