Home / Packages / @aaronontheweb/csharp-coding-style

@aaronontheweb/csharp-coding-style

csharp coding style for .NET development

prpm install @aaronontheweb/csharp-coding-style
0 total downloads

📄 Full Prompt Content

---
description: his file provides guidelines for writing clean, maintainable, and idiomatic C# code with a focus on functional patterns and proper abstraction.
globs: *.cs
alwaysApply: false
---

Role Definition:
 - C# Language Expert
 - Software Architect
 - Code Quality Specialist

General:
  Description: >
    C# code should be written to maximize readability, maintainability, and correctness
    while minimizing complexity and coupling. Prefer functional patterns and immutable
    data where appropriate, and keep abstractions simple and focused.
  Requirements:
    - Write clear, self-documenting code
    - Keep abstractions simple and focused
    - Minimize dependencies and coupling
    - Use modern C# features appropriately

Type Definitions:
  - Prefer records for data types:
      ```csharp
      // Good: Immutable data type with value semantics
      public sealed record CustomerDto(string Name, Email Email);
      
      // Avoid: Class with mutable properties
      public class Customer
      {
          public string Name { get; set; }
          public string Email { get; set; }
      }
      ```
  - Make classes sealed by default:
      ```csharp
      // Good: Sealed by default
      public sealed class OrderProcessor
      {
          // Implementation
      }
      
      // Only unsealed when inheritance is specifically designed for
      public abstract class Repository<T>
      {
          // Base implementation
      }
      ```
  - Use value objects to avoid primitive obsession:
      ```csharp
      // Good: Strong typing with value objects
      public sealed record OrderId(Guid Value)
      {
          public static OrderId New() => new(Guid.NewGuid());
          public static OrderId From(string value) => new(Guid.Parse(value));
      }
      
      // Avoid: Primitive types for identifiers
      public class Order
      {
          public Guid Id { get; set; }  // Primitive obsession
      }
      ```

Functional Patterns:
  - Use pattern matching effectively:
      ```csharp
      // Good: Clear pattern matching
      public decimal CalculateDiscount(Customer customer) =>
          customer switch
          {
              { Tier: CustomerTier.Premium } => 0.2m,
              { OrderCount: > 10 } => 0.1m,
              _ => 0m
          };
      
      // Avoid: Nested if statements
      public decimal CalculateDiscount(Customer customer)
      {
          if (customer.Tier == CustomerTier.Premium)
              return 0.2m;
          if (customer.OrderCount > 10)
              return 0.1m;
          return 0m;
      }
      ```
  - Prefer pure methods:
      ```csharp
      // Good: Pure function
      public static decimal CalculateTotalPrice(
          IEnumerable<OrderLine> lines,
          decimal taxRate) =>
          lines.Sum(line => line.Price * line.Quantity) * (1 + taxRate);
      
      // Avoid: Method with side effects
      public void CalculateAndUpdateTotalPrice()
      {
          this.Total = this.Lines.Sum(l => l.Price * l.Quantity);
          this.UpdateDatabase();
      }
      ```

Code Organization:
  - Separate state from behavior:
      ```csharp
      // Good: Behavior separate from state
      public sealed record Order(OrderId Id, List<OrderLine> Lines);
      
      public static class OrderOperations
      {
          public static decimal CalculateTotal(Order order) =>
              order.Lines.Sum(line => line.Price * line.Quantity);
      }
      ```
  - Use extension methods appropriately:
      ```csharp
      // Good: Extension method for domain-specific operations
      public static class OrderExtensions
      {
          public static bool CanBeFulfilled(this Order order, Inventory inventory) =>
              order.Lines.All(line => inventory.HasStock(line.ProductId, line.Quantity));
      }
      ```

Dependency Management:
  - Minimize constructor injection:
      ```csharp
      // Good: Minimal dependencies
      public sealed class OrderProcessor
      {
          private readonly IOrderRepository _repository;
          
          public OrderProcessor(IOrderRepository repository)
          {
              _repository = repository;
          }
      }
      
      // Avoid: Too many dependencies
      public class OrderProcessor
      {
          public OrderProcessor(
              IOrderRepository repository,
              ILogger logger,
              IEmailService emailService,
              IMetrics metrics,
              IValidator validator)
          {
              // Too many dependencies indicates possible design issues
          }
      }
      ```
  - Prefer composition with interfaces:
      ```csharp
      // Good: Composition with interfaces
      public sealed class EnhancedLogger : ILogger
      {
          private readonly ILogger _baseLogger;
          private readonly IMetrics _metrics;
          
          public EnhancedLogger(ILogger baseLogger, IMetrics metrics)
          {
              _baseLogger = baseLogger;
              _metrics = metrics;
          }
      }
      ```

Code Clarity:
    - Prefer range indexers over LINQ:
      ```csharp
      // Good: Using range indexers with clear comments
      var lastItem = items[^1];  // ^1 means "1 from the end"
      var firstThree = items[..3];  // ..3 means "take first 3 items"
      var slice = items[2..5];  // take items from index 2 to 4 (5 exclusive)
      
      // Avoid: Using LINQ when range indexers are clearer
      var lastItem = items.LastOrDefault();
      var firstThree = items.Take(3).ToList();
      var slice = items.Skip(2).Take(3).ToList();
      ```
  - Use meaningful names:
      ```csharp
      // Good: Clear intent
      public async Task<Result<Order>> ProcessOrderAsync(
          OrderRequest request,
          CancellationToken cancellationToken)
      
      // Avoid: Unclear abbreviations
      public async Task<Result<T>> ProcAsync<T>(ReqDto r, CancellationToken ct)
      ```

Error Handling:
  - Use Result types for expected failures:
      ```csharp
      // Good: Explicit error handling
      public sealed record Result<T>
      {
          public T? Value { get; }
          public Error? Error { get; }
          
          private Result(T value) => Value = value;
          private Result(Error error) => Error = error;
          
          public static Result<T> Success(T value) => new(value);
          public static Result<T> Failure(Error error) => new(error);
      }
      ```
  - Prefer exceptions for exceptional cases:
      ```csharp
      // Good: Exception for truly exceptional case
      public static OrderId From(string value)
      {
          if (!Guid.TryParse(value, out var guid))
              throw new ArgumentException("Invalid OrderId format", nameof(value));
          
          return new OrderId(guid);
      }
      ```

Testing Considerations:
  - Design for testability:
      ```csharp
      // Good: Easy to test pure functions
      public static class PriceCalculator
      {
          public static decimal CalculateDiscount(
              decimal price,
              int quantity,
              CustomerTier tier) =>
              // Pure calculation
      }
      
      // Avoid: Hard to test due to hidden dependencies
      public decimal CalculateDiscount()
      {
          var user = _userService.GetCurrentUser();  // Hidden dependency
          var settings = _configService.GetSettings(); // Hidden dependency
          // Calculation
      }
      ```

Immutable Collections:
  - Use System.Collections.Immutable with records:
      ```csharp
      // Good: Immutable collections in records
      public sealed record Order(
          OrderId Id, 
          ImmutableList<OrderLine> Lines,
          ImmutableDictionary<string, string> Metadata);
      
      // Avoid: Mutable collections in records
      public record Order(
          OrderId Id,
          List<OrderLine> Lines,  // Can be modified after creation
          Dictionary<string, string> Metadata);
      ```
  - Initialize immutable collections efficiently:
      ```csharp
      // Good: Using builder pattern
      var builder = ImmutableList.CreateBuilder<OrderLine>();
      foreach (var line in lines)
      {
          builder.Add(line);
      }
      return new Order(id, builder.ToImmutable());
      
      // Also Good: Using collection initializer
      return new Order(
          id,
          lines.ToImmutableList(),
          metadata.ToImmutableDictionary());
      ```

// ... existing code ...

Error Handling:
  - Use Result types for expected failures:
      ```csharp
      // Good: Explicit error handling
      public sealed record Result<T>
      {
          public T? Value { get; }
          public Error? Error { get; }
          
          private Result(T value) => Value = value;
          private Result(Error error) => Error = error;
          
          public static Result<T> Success(T value) => new(value);
          public static Result<T> Failure(Error error) => new(error);
      }
      ```
  - Prefer exceptions for exceptional cases:
      ```csharp
      // Good: Exception for truly exceptional case
      public static OrderId From(string value)
      {
          if (!Guid.TryParse(value, out var guid))
              throw new ArgumentException("Invalid OrderId format", nameof(value));
          
          return new OrderId(guid);
      }
      ```

Safe Operations:
  - Use Try methods for safer operations:
      ```csharp
      // Good: Using TryGetValue for dictionary access
      if (dictionary.TryGetValue(key, out var value))
      {
          // Use value safely here
      }
      else
      {
          // Handle missing key case
      }

      // Avoid: Direct indexing which can throw
      var value = dictionary[key];  // Throws if key doesn't exist

      // Good: Using Uri.TryCreate for URL parsing
      if (Uri.TryCreate(urlString, UriKind.Absolute, out var uri))
      {
          // Use uri safely here
      }
      else
      {
          // Handle invalid URL case
      }

      // Avoid: Direct Uri creation which can throw
      var uri = new Uri(urlString);  // Throws on invalid URL

      // Good: Using int.TryParse for number parsing
      if (int.TryParse(input, out var number))
      {
          // Use number safely here
      }
      else
      {
          // Handle invalid number case
      }

      // Good: Combining Try methods with null coalescing
      var value = dictionary.TryGetValue(key, out var result)
          ? result
          : defaultValue;

      // Good: Using Try methods in LINQ with pattern matching
      var validNumbers = strings
          .Select(s => (Success: int.TryParse(s, out var num), Value: num))
          .Where(x => x.Success)
          .Select(x => x.Value);
      ```
      
  - Prefer Try methods over exception handling:
      ```csharp
      // Good: Using Try method
      if (decimal.TryParse(priceString, out var price))
      {
          // Process price
      }

      // Avoid: Exception handling for expected cases
      try
      {
          var price = decimal.Parse(priceString);
          // Process price
      }
      catch (FormatException)
      {
          // Handle invalid format
      }
      ```

Asynchronous Programming:
  - Avoid async void:
      ```csharp
      // Good: Async method returns Task
      public async Task ProcessOrderAsync(Order order)
      {
          await _repository.SaveAsync(order);
      }
      
      // Avoid: Async void can crash your application
      public async void ProcessOrder(Order order)
      {
          await _repository.SaveAsync(order);
      }
      ```
  - Use Task.FromResult for pre-computed values:
      ```csharp
      // Good: Return pre-computed value
      public Task<int> GetDefaultQuantityAsync() =>
          Task.FromResult(1);
      
      // Better: Use ValueTask for zero allocations
      public ValueTask<int> GetDefaultQuantityAsync() =>
          new ValueTask<int>(1);
      
      // Avoid: Unnecessary thread pool usage
      public Task<int> GetDefaultQuantityAsync() =>
          Task.Run(() => 1);
      ```
  - Always flow CancellationToken:
      ```csharp
      // Good: Propagate cancellation
      public async Task<Order> ProcessOrderAsync(
          OrderRequest request,
          CancellationToken cancellationToken)
      {
          var order = await _repository.GetAsync(
              request.OrderId, 
              cancellationToken);
              
          await _processor.ProcessAsync(
              order, 
              cancellationToken);
              
          return order;
      }
      ```
  - Prefer await over ContinueWith:
      ```csharp
      // Good: Using await
      public async Task<Order> ProcessOrderAsync(OrderId id)
      {
          var order = await _repository.GetAsync(id);
          await _validator.ValidateAsync(order);
          return order;
      }
      
      // Avoid: Using ContinueWith
      public Task<Order> ProcessOrderAsync(OrderId id)
      {
          return _repository.GetAsync(id)
              .ContinueWith(t => 
              {
                  var order = t.Result; // Can deadlock
                  return _validator.ValidateAsync(order);
              });
      }
      ```
  - Never use Task.Result or Task.Wait:
      ```csharp
      // Good: Async all the way
      public async Task<Order> GetOrderAsync(OrderId id)
      {
          return await _repository.GetAsync(id);
      }
      
      // Avoid: Blocking on async code
      public Order GetOrder(OrderId id)
      {
          return _repository.GetAsync(id).Result; // Can deadlock
      }
      ```
  - Use TaskCompletionSource correctly:
      ```csharp
      // Good: Using RunContinuationsAsynchronously
      private readonly TaskCompletionSource<Order> _tcs = 
          new(TaskCreationOptions.RunContinuationsAsynchronously);
      
      // Avoid: Default TaskCompletionSource can cause deadlocks
      private readonly TaskCompletionSource<Order> _tcs = new();
      ```
  - Always dispose CancellationTokenSources:
      ```csharp
      // Good: Proper disposal of CancellationTokenSource
      public async Task<Order> GetOrderWithTimeout(OrderId id)
      {
          using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
          return await _repository.GetAsync(id, cts.Token);
      }
      ```
  - Prefer async/await over direct Task return:
      ```csharp
      // Good: Using async/await
      public async Task<Order> ProcessOrderAsync(OrderRequest request)
      {
          await _validator.ValidateAsync(request);
          var order = await _factory.CreateAsync(request);
          return order;
      }
      
      // Avoid: Manual task composition
      public Task<Order> ProcessOrderAsync(OrderRequest request)
      {
          return _validator.ValidateAsync(request)
              .ContinueWith(t => _factory.CreateAsync(request))
              .Unwrap();
      }
      ```

Nullability:
  - Enable nullable reference types:
      ```xml
      <PropertyGroup>
        <Nullable>enable</Nullable>
        <WarningsAsErrors>nullable</WarningsAsErrors>
      </PropertyGroup>
      ```
  - Mark nullable fields explicitly:
      ```csharp
      // Good: Explicit nullability
      public class OrderProcessor
      {
          private readonly ILogger<OrderProcessor>? _logger;
          private string? _lastError;
          
          public OrderProcessor(ILogger<OrderProcessor>? logger = null)
          {
              _logger = logger;
          }
      }
      
      // Avoid: Implicit nullability
      public class OrderProcessor
      {
          private readonly ILogger<OrderProcessor> _logger; // Warning: Could be null
          private string _lastError; // Warning: Could be null
      }
      ```
  - Use null checks appropriately:
      ```csharp
      // Good: Proper null checking
      public void ProcessOrder(Order? order)
      {
          if (order is null)
              throw new ArgumentNullException(nameof(order));
              
          _logger?.LogInformation("Processing order {Id}", order.Id);
      }
      
      // Good: Using pattern matching for null checks
      public decimal CalculateTotal(Order? order) =>
          order switch
          {
              null => throw new ArgumentNullException(nameof(order)),
              { Lines: null } => throw new ArgumentException("Order lines cannot be null", nameof(order)),
              _ => order.Lines.Sum(l => l.Total)
          };
      ```
  - Use null-forgiving operator when appropriate:
      ```csharp
      public class OrderValidator
      {
          private readonly IValidator<Order> _validator;
          
          public OrderValidator(IValidator<Order> validator)
          {
              _validator = validator ?? throw new ArgumentNullException(nameof(validator));
          }
          
          public ValidationResult Validate(Order order)
          {
              // We know _validator can't be null due to constructor check
              return _validator!.Validate(order);
          }
      }
      ```
  - Use nullability attributes:
      ```csharp
      public class StringUtilities
      {
          // Output is non-null if input is non-null
          [return: NotNullIfNotNull(nameof(input))]
          public static string? ToUpperCase(string? input) =>
              input?.ToUpperInvariant();
          
          // Method never returns null
          [return: NotNull]
          public static string EnsureNotNull(string? input) =>
              input ?? string.Empty;
          
          // Parameter must not be null when method returns true
          public static bool TryParse(string? input, [NotNullWhen(true)] out string? result)
          {
              result = null;
              if (string.IsNullOrEmpty(input))
                  return false;
                  
              result = input;
              return true;
          }
      }
      ```
  - Use init-only properties with non-null validation:
      ```csharp
      // Good: Non-null validation in constructor
      public sealed record Order
      {
          public required OrderId Id { get; init; }
          public required ImmutableList<OrderLine> Lines { get; init; }
          
          public Order()
          {
              Id = null!; // Will be set by required property
              Lines = null!; // Will be set by required property
          }
          
          private Order(OrderId id, ImmutableList<OrderLine> lines)
          {
              Id = id;
              Lines = lines;
          }
          
          public static Order Create(OrderId id, IEnumerable<OrderLine> lines) =>
              new(id, lines.ToImmutableList());
      }
      ```
  - Document nullability in interfaces:
      ```csharp
      public interface IOrderRepository
      {
          // Explicitly shows that null is a valid return value
          Task<Order?> FindByIdAsync(OrderId id, CancellationToken ct = default);
          
          // Method will never return null
          [return: NotNull]
          Task<IReadOnlyList<Order>> GetAllAsync(CancellationToken ct = default);
          
          // Parameter cannot be null
          Task SaveAsync([NotNull] Order order, CancellationToken ct = default);
      }
      ```

Symbol References:
  - Always use nameof operator:
      ```csharp
      // Good: Using nameof for parameter names
      public void ProcessOrder(Order order)
      {
          if (order is null)
              throw new ArgumentNullException(nameof(order));
      }
      
      // Good: Using nameof for property names
      public class Customer
      {
          private string _email;
          
          public string Email
          {
              get => _email;
              set => _email = value ?? throw new ArgumentNullException(nameof(value));
          }
          
          public void UpdateEmail(string newEmail)
          {
              if (string.IsNullOrEmpty(newEmail))
                  throw new ArgumentException("Email cannot be empty", nameof(newEmail));
              
              Email = newEmail;
          }
      }
      
      // Good: Using nameof in attributes
      public class OrderProcessor
      {
          [Required(ErrorMessage = "The {0} field is required")]
          [Display(Name = nameof(OrderId))]
          public string OrderId { get; init; }
          
          [MemberNotNull(nameof(_repository))]
          private void InitializeRepository()
          {
              _repository = new OrderRepository();
          }
          
          [NotifyPropertyChangedFor(nameof(FullName))]
          public string FirstName
          {
              get => _firstName;
              set => SetProperty(ref _firstName, value);
          }
      }
      
      // Avoid: Hard-coded string references
      public void ProcessOrder(Order order)
      {
          if (order is null)
              throw new ArgumentNullException("order"); // Breaks refactoring
      }
      ```
  - Use nameof with exceptions:
      ```csharp
      public class OrderService
      {
          public async Task<Order> GetOrderAsync(OrderId id, CancellationToken ct)
          {
              var order = await _repository.FindAsync(id, ct);
              
              if (order is null)
                  throw new OrderNotFoundException(
                      $"Order with {nameof(id)} '{id}' not found");
                      
              if (!order.Lines.Any())
                  throw new InvalidOperationException(
                      $"{nameof(order.Lines)} cannot be empty");
                      
              return order;
          }
          
          public void ValidateOrder(Order order)
          {
              ArgumentNullException.ThrowIfNull(order, nameof(order));
              
              if (order.Lines.Count == 0)
                  throw new ArgumentException(
                      "Order must have at least one line",
                      nameof(order));
          }
      }
      ```
  - Use nameof in logging:
      ```csharp
      public class OrderProcessor
      {
          private readonly ILogger<OrderProcessor> _logger;
          
          public async Task ProcessAsync(Order order)
          {
              _logger.LogInformation(
                  "Starting {Method} for order {OrderId}",
                  nameof(ProcessAsync),
                  order.Id);
                  
              try
              {
                  await ProcessInternalAsync(order);
              }
              catch (Exception ex)
              {
                  _logger.LogError(
                      ex,
                      "Error in {Method} for {Property} {Value}",
                      nameof(ProcessAsync),
                      nameof(order.Id),
                      order.Id);
                  throw;
              }
          }
      }
      ```

# End of Cursor Rules File 

💡 Suggested Test Inputs

Loading suggested inputs...

🎯 Community Test Results

Loading results...

📦 Package Info

Format
cursor
Type
rule
Category
languages
License
Apache-2.0

🔗 Links