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
231: Nullable Attributes: NotNull, MaybeNull
Imagine you're managing a high-end warehouse. Normally, every box has a standard label: "Contains Glass" or "Standard Cargo." Your staff knows exactly how to handle them based on those labels. But every now and then, you get a weird shipment. Maybe a box is labeled "Standard Cargo," but you happen to know from the supplier's email that it actually contains something fragile. Or, you have a box that looks like it's empty, but you've personally verified there's a tiny, high-value chip inside.
In these cases, the standard label isn't enough. You need a "special handling" sticker—something that overrides the standard label to tell your staff, "I know what the label says, but treat this differently."
That is exactly what NotNull and MaybeNull attributes do for the C# compiler. The "standard labels" are your nullable reference types (string? vs string). The "special stickers" are these attributes, which you use when the compiler's static analysis isn't smart enough to understand the reality of your code.
When the Compiler Stops Trusting You
Most of the time, the C# compiler is great at tracking nulls. But there are "blind spots." This usually happens in complex methods where the return value depends on logic the compiler can't follow, or when you're dealing with legacy APIs. You'll start seeing those annoying warnings: "Possible null reference return."
You could just use the null-forgiving operator (!), but that's a blunt instrument. Attributes are more surgical. They don't just silence a warning; they provide a contract for anyone calling your method.
The 'Trust Me, It's Actually There' Guarantee
Let's look at [NotNull]. Use this when you have a parameter that the compiler thinks could be null, but if the method returns a certain value (like true), that parameter is guaranteed to be non-null from that point forward.
I see this all the time in "Try" patterns. Check this out:
using System.Diagnostics.CodeAnalysis;
public class UserSession
{
private string? _sessionToken;
// We tell the compiler: "If this returns true, 'token' is definitely not null"
public bool TryGetToken([NotNull] out string? token)
{
if (_sessionToken != null)
{
token = _sessionToken;
return true;
}
token = null;
return false;
}
}
// Now, the consumer gets a better experience:
var session = new UserSession();
if (session.TryGetToken(out var token))
{
// The compiler knows 'token' is not null here because of the [NotNull] attribute.
// No warning when calling token.Length!
Console.WriteLine(token.Length);
}
Without that attribute, the compiler would still see token as string? inside that if block, and you'd be forced to use token!.Length. It's much cleaner to just tell the compiler how the method actually behaves.
Admitting the Type System is Lying
Then we have [MaybeNull]. This is the opposite. You use this when your method signature claims to return a non-nullable type (string), but there are specific edge cases where it might actually return null, and you want to warn the caller (and the analyzer) about it.
I'll be honest: you won't use this as often as NotNull, but it's a lifesaver when wrapping old libraries that don't have nullable annotations. It's like saying, "The documentation says this is a string, but in the real world, it might be null, so don't trust it blindly."
using System.Diagnostics.CodeAnalysis;
public class LegacyDatabaseWrapper
{
// The signature says it returns a non-nullable string,
// but the [MaybeNull] attribute warns us it might still be null.
[MaybeNull]
public string GetOldSetting(string key)
{
// Imagine some ancient C++ interop happening here
// that returns null if the key isn't found.
return NativeMethods.FetchSetting(key);
}
}
By adding [MaybeNull], you're being honest with the rest of your team. You're signaling that while the type system is optimistic, the reality is a bit messier.
📋 Practical Task
Hardening a Legacy User Profile Cache
You've inherited a UserProfileCache class. The compiler is throwing warnings because it can't figure out the nullability of the out parameter in the TryGetProfile method, and it's blindly trusting a legacy FetchRawData method that is known to occasionally return nulls despite its return type.
Your Task:
- Add the
[NotNull]attribute to theprofileparameter inTryGetProfileso that the caller doesn't get a warning when accessing the profile after a successful return. - Add the
[MaybeNull]attribute to theFetchRawDatamethod to signal that the returned string might actually be null.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
public class UserProfile
{
public string Name { get; set; } = "";
}
public class UserProfileCache
{
private Dictionary<int, UserProfile> _cache = new();
// TODO: Apply the correct attribute here
public bool TryGetProfile(int id, out UserProfile? profile)
{
if (_cache.TryGetValue(id, out var p))
{
profile = p;
return true;
}
profile = null;
return false;
}
// TODO: Apply the correct attribute here
public string FetchRawData(int id)
{
// Simulating a legacy call that might return null
return id == 0 ? null! : "User Data";
}
}
public class Program
{
public static void Main()
{
var cache = new UserProfileCache();
if (cache.TryGetProfile(1, out var profile))
{
// This should not produce a warning after your fix
Console.WriteLine(profile.Name);
}
}
}
There are no comments for now.