- C# 14 Mastery Series Part 1: Introduction to C# 14 – What’s New and Why It Matters
- C# 14 Mastery Series Part 2: Field-Backed Properties Deep Dive – The `field` Keyword Revolution
- C# 14 Mastery Series Part 3: Extension Members Foundation – New Syntax and Capabilities
- C# 14 Mastery Series Part 5: Generic Types Enhancement – nameof with Unbound Generics
- C# 14 Mastery Series Part 4: Advanced Extension Members – Properties and Complex Scenarios
- C# 14 Mastery Series Part 6: Performance Analysis – Benchmarking C# 14 Features
- C# 14 Mastery Series Part 7: Migration Strategies – From C# 13 to C# 14
- C# 14 Mastery Series Part 8: Real-World Implementation – Building Production Applications
Welcome to Part 4 of our C# 14 mastery series! Building on the foundation we established in Part 3, we’re now exploring the advanced capabilities of extension members. Today we’ll dive deep into instance and static properties within extensions, complex architectural patterns, and real-world scenarios that showcase the true power of C# 14’s enhanced extension system.
Instance Properties in Extension Members
One of the most exciting additions in C# 14 is the ability to add properties to existing types through extensions. This opens up entirely new possibilities for creating fluent, intuitive APIs.
Basic Property Extensions
extension PersonExtensions for Person
{
// Read-only computed property
public string DisplayName => string.IsNullOrWhiteSpace(this.MiddleName)
? $"{this.FirstName} {this.LastName}"
: $"{this.FirstName} {this.MiddleName} {this.LastName}";
// Property with validation
public string FormattedPhoneNumber
{
get => FormatPhoneNumber(this.PhoneNumber);
set => this.PhoneNumber = ValidateAndCleanPhoneNumber(value);
}
// Age calculation property
public int Age => DateTime.Today.Year - this.DateOfBirth.Year
- (DateTime.Today.DayOfYear < this.DateOfBirth.DayOfYear ? 1 : 0);
}
State-Aware Properties
extension OrderExtensions for Order
{
// Complex business logic properties
public bool CanBeCancelled => this.Status == OrderStatus.Pending &&
this.CreatedDate > DateTime.UtcNow.AddHours(-24);
public bool RequiresApproval => this.TotalAmount > 10000 ||
this.Items.Any(i => i.Category == "Restricted");
public decimal TaxAmount => this.Items.Sum(i => i.Price * i.Quantity * i.TaxRate);
public OrderPriority Priority
{
get => CalculatePriority();
set => UpdatePriorityMetadata(value);
}
public string StatusDescription => this.Status switch
{
OrderStatus.Pending => "Awaiting processing",
OrderStatus.Processing => $"Being processed (ETA: {this.EstimatedCompletionDate:MM/dd})",
OrderStatus.Shipped => $"Shipped via {this.ShippingMethod}",
OrderStatus.Delivered => $"Delivered on {this.DeliveredDate:MM/dd/yyyy}",
OrderStatus.Cancelled => "Order cancelled",
_ => "Unknown status"
};
}
Static Properties in Extensions
Configuration and Defaults
extension HttpClientExtensions for HttpClient
{
// Static configuration properties
public static TimeSpan DefaultTimeout { get; set; } = TimeSpan.FromSeconds(30);
public static string DefaultUserAgent { get; set; } = "MyApp/1.0";
public static int MaxRetryAttempts { get; set; } = 3;
// Static factory properties
public static HttpClient DefaultInstance => CreateConfiguredClient();
public static HttpClient RetryEnabledInstance => CreateRetryClient();
// Instance properties for enhanced functionality
public bool IsConfiguredForRetries => this.Timeout == DefaultTimeout &&
this.DefaultRequestHeaders.UserAgent.ToString().Contains("Retry");
public int PendingRequestCount
{
get => GetPendingRequestCount();
set => throw new NotSupportedException("Pending request count is read-only");
}
}
Complex Architectural Patterns
Repository Pattern with Extension Properties
extension RepositoryExtensions<T> for IRepository<T> where T : class
{
// Computed properties for repository state
public bool HasData => this.Count() > 0;
public bool IsEmpty => !HasData;
// Cache-aware properties
public bool IsCacheEnabled
{
get => GetCacheSettings().Enabled;
set => UpdateCacheSettings(enabled: value);
}
public TimeSpan CacheExpiration
{
get => GetCacheSettings().Expiration;
set => UpdateCacheSettings(expiration: value);
}
// Performance monitoring properties
public PerformanceMetrics Metrics => GetPerformanceMetrics();
public double AverageQueryTime => Metrics.AverageResponseTime;
// Static configuration
public static int DefaultPageSize { get; set; } = 25;
public static int MaxPageSize { get; set; } = 1000;
}
Command Pattern Enhancement
extension CommandExtensions for ICommand
{
// State properties
public bool IsExecuting
{
get => GetExecutionState().IsRunning;
set => throw new NotSupportedException("Execution state is managed internally");
}
public DateTime? LastExecuted => GetExecutionState().LastExecuted;
public TimeSpan? LastExecutionDuration => GetExecutionState().LastDuration;
// Validation properties
public bool HasValidationErrors => GetValidationErrors().Any();
public IEnumerable<string> ValidationErrors => GetValidationErrors();
// Static configuration for command pipeline
public static TimeSpan DefaultTimeout { get; set; } = TimeSpan.FromMinutes(5);
public static int MaxConcurrentCommands { get; set; } = 10;
public static bool EnableAuditLogging { get; set; } = true;
}
Performance Considerations
Property Caching Strategies
extension ExpensiveComputationExtensions for DataModel
{
private static readonly ConcurrentDictionary<int, string> _computedValuesCache = new();
// Expensive computation with caching
public string ExpensiveComputedValue
{
get
{
var key = this.GetHashCode();
return _computedValuesCache.GetOrAdd(key, _ => PerformExpensiveComputation());
}
}
// Cache invalidation when data changes
public void InvalidateCache()
{
var key = this.GetHashCode();
_computedValuesCache.TryRemove(key, out _);
}
// Static cache management
public static void ClearAllCaches() => _computedValuesCache.Clear();
public static int CachedItemCount => _computedValuesCache.Count;
}
Best Practices Summary
When implementing advanced extension members, follow these guidelines:
- Performance: Use caching judiciously and consider memory implications
- Error Handling: Always provide safe fallbacks and meaningful error messages
- Consistency: Maintain consistent naming and behavior patterns across related extensions
- Documentation: Document complex properties and their side effects
- Testing: Thoroughly test property getters and setters, especially those with side effects
Conclusion
Advanced extension members in C# 14 open up powerful possibilities for creating sophisticated, maintainable APIs. By combining instance properties, static properties, and complex logic within extension blocks, you can create extensions that feel like natural parts of the extended types.
The patterns we've explored—from repository enhancements to configuration management—demonstrate how extension members can solve real-world architectural challenges while maintaining clean, readable code.
In Part 5, we'll shift focus to explore the enhanced generic type system in C# 14, particularly the powerful improvements to the nameof
operator and their implications for metaprogramming and code generation scenarios.
Next: Part 5 - Generic Types Enhancement!