C# 14 Mastery Series Part 4: Advanced Extension Members – Properties and Complex Scenarios

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!

Navigate<< C# 14 Mastery Series Part 5: Generic Types Enhancement – nameof with Unbound GenericsC# 14 Mastery Series Part 6: Performance Analysis – Benchmarking C# 14 Features >>

Written by:

265 Posts

View All Posts
Follow Me :