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
229: Nullable Annotations on APIs
Imagine you're ordering from a restaurant menu. Most items are straightforward: "Cheeseburger" means you get a cheeseburger. But then you see "Seasonal Soup (subject to availability)." That little parenthetical is a contract. It's the restaurant telling you, "Look, we'll try to give you soup, but there's a real chance the waiter is going to come back and tell you we're out." Because of that warning, you don't just sit there blindly expecting soup; you have a backup plan—maybe you'll order a side salad instead.
In C#, nullable annotations on your public APIs are exactly like that "subject to availability" note. Without them, a developer using your library is guessing. They see a method that returns a string and wonder, "Will this actually return null if the record isn't found, or will it throw an exception? Do I need to wrap this in a null check, or am I being overly cautious?"
The Contract Between You and Your Caller
When you're writing a library or a service that other people (or even just other teams) will use, your method signatures are your primary form of communication. By using Nullable Reference Types (NRTs), you move the conversation from "I hope the documentation is up to date" to "the compiler will literally tell you if you're forgetting something."
Take this simple API for a user profile service. Notice how the annotations change the expectations for the person calling the code:
public class UserProfileApi
{
// The '?' tells the caller: "Prepare for the possibility that this user doesn't exist."
public User? GetUserById(int id)
{
return _database.Users.Find(id);
}
// No '?' here. The caller can trust that they will ALWAYS get a string back.
public string GetSystemVersion()
{
return "v2.4.1";
}
}
If I'm the developer consuming this API, the User? tells me I must handle the null case. If I try to access GetUserById(123).Name without a check, the compiler will give me a warning. Meanwhile, I can call GetSystemVersion().Length with total confidence. I've offloaded the mental burden of "what if this is null?" onto the type system.
Signaling Intent with the Question Mark
It's not just about return values; it's about what you're asking for in your parameters. This is where I see a lot of developers slip up. If your method can handle a null input, mark it as nullable. If a null input will cause the method to fail or throw an ArgumentNullException, keep it non-nullable.
I've spent way too many hours debugging production crashes because someone passed a null into a "required" field that wasn't marked as such. Here is how you should distinguish them:
public void UpdateUserEmail(int id, string newEmail)
{
// The caller knows newEmail MUST be provided.
// I don't even need to check for null if I trust my static analysis.
_database.Users.UpdateEmail(id, newEmail);
}
public void UpdateUserBio(int id, string? bio)
{
// The '?' tells the caller: "It's okay to pass null if you want to clear the bio."
_database.Users.UpdateBio(id, bio);
}
The Danger of Lying to Your Users
Here is my biggest piece of advice: Don't lie in your signatures.
It's tempting to mark a return type as string just to avoid making the caller deal with nulls. But if your code actually returns null at runtime, you've just created a landmine. The caller's compiler told them it was safe, so they didn't write a null check, and now they're hitting a NullReferenceException in production. That's actually worse than not using annotations at all, because you've given them a false sense of security.
If there is even a 1% chance that a value could be null, mark it with a ?. It is always better to force a developer to handle a null they didn't expect than to let them ignore a null that eventually crashes their app.
📋 Practical Task
Refactoring the LibraryCatalog API for Null Safety
You have been handed a legacy LibraryCatalog class. Currently, it uses standard reference types, leaving the API consumers guessing about nullability. Your task is to apply nullable annotations to the API to make the contracts explicit.
Requirements:
GetBookByIsbn(string isbn): Should be marked as returning a nullableBook?because a book might not exist in the catalog.GetCategoryName(int categoryId): Should return a non-nullablestringbecause the system guarantees every category ID has a name.SearchBooks(string? query): The query parameter should be nullable, as passing null should be interpreted as "return all books."AddBook(Book book): The book parameter must be non-nullable; the method cannot function without a book object.
public class Book { public string Title { get; set; } }
public class LibraryCatalog
{
public Book GetBookByIsbn(string isbn)
{
/* implementation */
return null;
}
public string GetCategoryName(int categoryId)
{
/* implementation */
return "Fiction";
}
public List<Book> SearchBooks(string query)
{
/* implementation */
return new List<Book>();
}
public void AddBook(Book book)
{
/* implementation */
}
}
Modify the signatures of the LibraryCatalog class to correctly reflect these API contracts using nullable annotations.
There are no comments for now.