Skip to main content

csharp-nullable-types

Use when C# nullable reference types and value types for null safety, nullable annotations, and patterns for handling null values.

Source facts

Repository
TheBushidoCollective/han
Last source activity
February 11, 2026 at 17:47
Detected SKILL.md language
English
Stars
198
Forks
22

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
csharp-nullable-types
user-invocable
false
description
Use when C# nullable reference types and value types for null safety, nullable annotations, and patterns for handling null values.
allowed-tools
["Read","Write","Edit","Grep","Glob","Bash"]
# C# Nullable Types Nullable types in C# help prevent null reference exceptions, one of the most common programming errors. C# 8.0 introduced nullable reference types, providing compile-time null-safety checking. This skill covers nullable value types, nullable reference types, null-coalescing operators, and patterns for safe null handling. ## Nullable Value Types Value types (int, double, bool, struct) normally cannot be null. Nullable value types enable representing "no value" state. ```csharp using System; public class NullableValueTypes { // Declaration public void Declaration() { int? nullableInt = null; int? anotherInt = 42; double? nullableDouble = null; bool? nullableBool = true; // Generic syntax (equivalent) Nullable<int> genericNullable = null; } // Checking for null public void NullChecking() { int? value = GetNullableValue(); // HasValue property if (value.HasValue) { int actualValue = value.Value; Console.WriteLine(actualValue); } // Comparison with null if (value != null) { Console.WriteLine(value.Value); } // Null-conditional operator int? doubled = value?.GetHashCode(); } // Getting values public void GettingValues() { int? value = 42; // Value property (throws if null) int val1 = value.Value; // GetValueOrDefault int val2 = value.GetValueOrDefault(); // Returns 0 if null int val3 = value.GetValueOrDefault(10); // Returns 10 if null // Null-coalescing operator int val4 = value ?? 0; // Returns 0 if null } // Nullable in expressions public void NullableExpressions() { int? a = 10; int? b = 20; int? c = null; // Arithmetic (null if any operand is null) int? sum1 = a + b; // 30 int? sum2 = a + c; // null // Comparison bool? equal = a == b; // false bool? greater = a > c; // null // Logical operators bool? and = (a > 5) & (c > 5); // null } // Nullable structs public struct Point { public int X { get; set; } public int Y { get; set; } } public void NullableStructs() { Point? point = null; if (point.HasValue) { int x = point.Value.X; int y = point.Value.Y; } // Null-conditional with structs int? x = point?.X; } private int? GetNullableValue() => null; } ``` ## Nullable Reference Types C# 8.0+ provides nullable reference types, enabling compile-time null-safety for reference types. ```csharp #nullable enable using System; using System.Collections.Generic; public class NullableReferenceTypes { // Non-nullable by default public string Name { get; set; } = string.Empty; // Explicitly nullable public string? MiddleName { get; set; } // Constructor public NullableReferenceTypes(string name, string? middleName = null) { Name = name; // Must be non-null MiddleName = middleName; // Can be null } // Method with nullable parameters public string FormatName(string firstName, string? middleName, string lastName) { // Compiler warns if middleName used without null check if (middleName != null) { return $"{firstName} {middleName} {lastName}"; } return $"{firstName} {lastName}"; } // Nullable return type public string? FindPerson(int id) { // May return null return id > 0 ? "Found" : null; } // Collections of nullable types public void CollectionExamples() { // List of non-null strings List<string> names = new List<string> { "Alice", "Bob" }; // List of nullable strings List<string?> nullableNames = new List<string?> { "Alice", null, "Charlie" }; // Dictionary with nullable values Dictionary<string, string?> dict = new Dictionary<string, string?> { { "key1", "value1" }, { "key2", null } }; } // Nullable generic types public T? FindById<T>(int id) where T : class { // Returns null if not found return default(T); } // Not-null assertion operator public void NotNullAssertion() { string? maybeName = GetName(); // Tell compiler this won't be null (use with caution!) string name = maybeName!; } private string? GetName() => null; } ``` ## Null-Coalescing Operators Null-coalescing operators provide concise null handling. ```csharp #nullable enable using System; using System.Collections.Generic; public class NullCoalescingOperators { // Null-coalescing operator (??) public string GetDisplayName(string? name) { // Returns "Unknown" if name is null return name ?? "Unknown"; } // Chaining public string GetFirstNonNull(string? a, string? b, string? c) { return a ?? b ?? c ?? "Default"; } // Null-coalescing assignment (??=) public void NullCoalescingAssignment() { string? name = null; // Assign only if null name ??= "Default Name"; // name is now "Default Name" name ??= "Another Name"; // name stays "Default Name" } // With collections public void CollectionCoalescing() { List<string>? list = null; // Initialize if null list ??= new List<string>(); // Add items list.Add("item"); } // In property initialization private List<string>? _items; public List<string> Items => _items ??= new List<string>(); // Complex expressions public string ComplexCoalescing(User? user) { // Multiple levels return user?.Profile?.DisplayName ?? user?.Email ?? "Guest"; } public class User { public Profile? Profile { get; set; } public string? Email { get; set; } } public class Profile { public string? DisplayName { get; set; } } } ``` ## Null-Conditional Operator The null-conditional operator safely accesses members and calls methods on potentially null references. ```csharp #nullable enable using System; using System.Collections.Generic; public class NullConditionalOperator { public class Person { public string Name { get; set; } = string.Empty; public Address? Address { get; set; } public List<string>? PhoneNumbers { get; set; } } public class Address { public string City { get; set; } = string.Empty; public string? PostalCode { get; set; } } // Basic null-conditional public string? GetCity(Person? person) { // Returns null if person or Address is null return person?.Address?.City; } // With indexers public string? GetFirstPhone(Person? person) { // Returns null if person, PhoneNumbers is null, or empty return person?.PhoneNumbers?[0]; } // With method calls public int? GetNameLength(Person? person) { return person?.Name.Length; } // Combining operators public string GetCityOrDefault(Person? person) { return person?.Address?.City ?? "Unknown"; } // With arrays public string? GetFirstItem(string[]? items) { return items?[0]; } // With delegates public void InvokeCallback(Action? callback) { callback?.Invoke(); } // With events public event EventHandler? DataChanged; public void OnDataChanged() { DataChanged?.Invoke(this, EventArgs.Empty); } // Chaining multiple operations public void ChainedOperations(Person? person) { string? result = person? .Address? .City .ToUpper() .Substring(0, 3); } } ``` ## Null Handling Patterns Common patterns for safely handling null values. ```csharp #nullable enable using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; public class NullHandlingPatterns { // Null object pattern public interface ILogger { void Log(string message); } public class ConsoleLogger : ILogger { public void Log(string message) => Console.WriteLine(message); } public class NullLogger : ILogger { public void Log(string message) { } } // Guard clauses public void ProcessData(string? data) { if (data == null) { throw new ArgumentNullException(nameof(data)); } // data is non-null here Console.WriteLine(data.Length); } // Early return public string FormatData(string? data) { if (data == null) return string.Empty; return data.ToUpper(); } // TryGet pattern public bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { // Simulate lookup if (key == "valid") { value = "Found"; return true; } value = null; return false; } public void UseTryGet() { if (TryGetValue("valid", out string? result)) { // result is guaranteed non-null here Console.WriteLine(result.Length); } } // MemberNotNull attribute private string? _cachedValue; [MemberNotNull(nameof(_cachedValue))] private void EnsureCacheLoaded() { _cachedValue ??= LoadValue(); } public void UseCache() { EnsureCacheLoaded(); // _cachedValue is guaranteed non-null Console.WriteLine(_cachedValue.Length); } // NotNullIfNotNull attribute [return: NotNullIfNotNull(nameof(input))] public string? Transform(string? input) { return input?.ToUpper(); } // AllowNull and DisallowNull private string _name = string.Empty; [AllowNull] public string Name { get => _name; set => _name = value ?? string.Empty; } private string LoadValue() => "loaded"; } ``` ## Nullable Annotations Attributes that provide additional null-state information to the compiler. ```csharp #nullable enable using System.Diagnostics.CodeAnalysis; using System.Collections.Generic; public class NullableAnnotations { // NotNull - parameter won't be null when method returns public void Initialize([NotNull] ref string? value) { value ??= "default"; } // MaybeNull - return may be null despite non-nullable type [return: MaybeNull] public T GetDefaultValue<T>() { return default(T); } // DoesNotReturn - method never returns normally [DoesNotReturn] public void ThrowError(string message) { throw new System.InvalidOperationException(message); } public void UseDoesNotReturn(string? value) { if (value == null) { ThrowError("Value is null"); } // Compiler knows value is not null here System.Console.WriteLine(value.Length); } // MemberNotNullWhen private string? _data; [MemberNotNullWhen(true, nameof(_data))] private bool IsDataLoaded() { return _data != null; } public void UseData() { if (IsDataLoaded()) { // _data is guaranteed non-null System.Console.WriteLine(_data.Length); } } // NotNullIfNotNull [return: NotNullIfNotNull(nameof(source))] public string? ProcessString(string? source) { return source?.Trim(); } } ``` ## Migration Strategies Strategies for adopting nullable reference types in existing code. ```csharp // Gradual migration approach // File 1: Keep nullability disabled #nullable disable public class LegacyClass { public string Name { get; set; } // Implicitly nullable } #nullable restore // File 2: Enable nullability for new code #nullable enable public class ModernClass { public string Name { get; set; } = string.Empty; // Non-nullable public string? MiddleName { get; set; } // Explicitly nullable } #nullable restore // File 3: Suppress warnings during migration #nullable enable public class MigratingClass { // Suppress specific warnings #pragma warning disable CS8618 public string Name { get; set; } #pragma warning restore CS8618 // Or use ! operator temporarily public string GetName() => Name!; } ``` ## Working with External Libraries Handling nullability when working with libraries that don't use nullable annotations. ```csharp #nullable enable using System; public class ExternalLibraryHandling { // Assume external library method // public string GetData() { ... } // Defensive programming public void UseExternalLibrary() { // Treat return as potentially null string? data = GetExternalData(); if (data != null) { ProcessData(data); } } // Create wrappers public string? GetExternalDataSafe() { string result = GetExternalData(); return string.IsNullOrEmpty(result) ? null : result; } // Extension methods for safety public static class StringExtensions { public static string OrEmpty(this string? value) { return value ?? string.Empty; } public static bool IsNullOrEmpty( [NotNullWhen(false)] this string? value) { return string.IsNullOrEmpty(value); }
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub