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
230: Null-Forgiving Operator
Sometimes, you and the C# compiler simply disagree. You know—based on the architecture of your app, a database constraint, or a validation check that happened five method calls ago—that a variable cannot possibly be null. The compiler, however, only sees the type definition and insists on giving you a warning. This is where the null-forgiving operator (the ! symbol) comes in.
Think of the ! operator as a "shush" button. You aren't changing the runtime behavior of the code; you aren't checking for nulls or providing defaults. You are simply telling the compiler, "I've got this. Stop worrying about this specific reference."
Setting up a "Trust Me" scenario
Let's look at a common situation: a class that gets populated by an external framework (like an ORM or a JSON deserializer). I'll build a simple UserSession class. In a real app, the SessionId would be guaranteed by the database, but the compiler doesn't know that.
public class UserSession
{
// The compiler sees this as nullable because it's not set in the constructor
public string? SessionId { get; set; }
public void LogCurrentSession()
{
// Warning: Possible null reference assignment.
Console.WriteLine($"Current Session: {SessionId.Length}");
}
}
If I try to access SessionId.Length, the compiler flags it. It's doing its job. But if this class is only ever instantiated after a successful login where the ID is guaranteed, the warning is just noise.
The temptation of the redundant check
When I first encountered this, my instinct was to just "fix" the warning with a standard null check. I'll show you what I did in a similar project a while back, and why it was actually a mistake in that context.
public void LogCurrentSession()
{
if (SessionId == null)
{
throw new InvalidOperationException("SessionId should never be null here.");
}
Console.WriteLine($"Current Session: {SessionId.Length}");
}
Now the warning is gone, but I've added a runtime branch and a potential exception to a piece of code that runs thousands of times a second. If I am 100% certain that the framework has already validated this object before it reached this method, I'm adding overhead and clutter for no real gain. I'm coding defensively to satisfy a compiler, not to handle a real business requirement.
Using the null-forgiving operator correctly
Instead of adding runtime checks, I can use the ! operator. I'll place it immediately after the variable name to tell C# that I am taking responsibility for this value.
public void LogCurrentSession()
{
// "Trust me, SessionId is not null."
Console.WriteLine($"Current Session: {SessionId!.Length}");
}
Notice that SessionId! doesn't actually do anything when the program runs. It's a compile-time instruction only. If SessionId actually happens to be null at runtime, you'll still get a NullReferenceException. That's why you should use this sparingly. It's a tool for when you have external knowledge that the compiler lacks.
I usually reserve this for three specific cases: unit tests where I've manually set up the state, legacy code integration, and properties initialized by dependency injection or database mappers. If you find yourself putting ! everywhere, you aren't "fixing" warnings—you're just turning off the safety features of the language.
📋 Practical Task
Fixing the ProductRepository Warning
You are working on a ProductRepository class. The _connectionString field is assigned during a Initialize() method that is called at application startup. Because it's not assigned in the constructor, the compiler is complaining that _connectionString might be null when used in the GetConnection() method.
Modify the code below to remove the compiler warning using the null-forgiving operator. Do not use an if statement or a null-coalescing operator, as the team has already decided that a runtime crash is preferable to a redundant check in this performance-critical path.
public class ProductRepository
{
private string? _connectionString;
public void Initialize(string connectionString)
{
_connectionString = connectionString;
}
public string GetConnection()
{
// TODO: Use the null-forgiving operator to remove the warning
// and return the _connectionString.
return _connectionString;
}
}There are no comments for now.