Option
The Option<TValue> monad represents an optional value that may or may not be present. It provides a type-safe alternative to null references, making the optionality explicit in the type system.
Purpose
Section titled “Purpose”Option<T> eliminates null reference exceptions by forcing explicit handling of the absence of a value. Instead of returning null and hoping consumers remember to check, Option<T> makes the optionality part of the method signature.
Producer Perspective
Section titled “Producer Perspective”Return Option<T> from methods that may not always have a value:
public class UserRepository{ public Option<User> FindById(UserId id) { var user = _database.Users.FirstOrDefault(u => u.Id == id);
// Implicit conversion from TValue to Option<TValue> if (user is not null) return user;
// Return None when value is absent return Option<User>.None(); }
public Option<string> GetEmailAddress(UserId userId) { var user = FindById(userId); if (!user.HasValue) return Option<string>.None();
if (user.TryGetValue(out var userValue) && !string.IsNullOrEmpty(userValue.Email)) return userValue.Email;
return Option<string>.None(); }}Consumer Perspective
Section titled “Consumer Perspective”Explicitly handle the presence or absence of values:
public class UserService{ readonly UserRepository _repository;
public void ProcessUser(UserId id) { var userOption = _repository.FindById(id);
// Check if value is present if (userOption.HasValue) { // TryGetValue provides safe access if (userOption.TryGetValue(out var user)) { Console.WriteLine($"Processing user: {user.Name}"); } } else { Console.WriteLine("User not found"); } }
public void DisplayUserEmail(UserId id) { var emailOption = _repository.GetEmailAddress(id);
// TryGetValue approach if (emailOption.TryGetValue(out var email)) { Console.WriteLine($"Email: {email}"); } else { Console.WriteLine("No email available"); } }
public string GetUserDisplayName(UserId id) { var userOption = _repository.FindById(id);
// Using Match() for functional composition return userOption.Match( value => value.Name, none => "Unknown User"); }
public void ProcessUserWithMatch(UserId id) { var userOption = _repository.FindById(id);
// Switch() for side effects (Match returns a value; Switch is for actions) userOption.Switch( value => Console.WriteLine($"Processing user: {value.Name}"), none => Console.WriteLine("User not found")); }
public void DisplayUserWithPatternMatching(UserId id) { var userOption = _repository.FindById(id);
// Pattern matching with switch expression var message = userOption switch { { IsT0: true } => $"Found user: {userOption.AsT0.Name}", _ => "User not found" };
Console.WriteLine(message); }}Key Points
Section titled “Key Points”- Use
Option<T>instead of returningnullfor optional values - The
HasValueproperty indicates if a value is present TryGetValueprovides safe value extraction similar to Dictionary’s TryGetValue pattern- Use
Match()for functional composition and transforming values - Pattern matching with
switchexpressions enables concise conditional logic - Implicit conversion from
TValuetoOption<TValue>simplifies creation - Makes API contracts explicit - callers know they must handle absence