code-standards
C# coding conventions, naming patterns, and file organization standards for the MotoRent project.
When & Why to Use This Skill
This Claude skill establishes a comprehensive framework for C# coding standards, naming conventions, and architectural patterns specifically optimized for the MotoRent project. It enforces modern .NET 10 and C# 14 features, ensuring code consistency, maintainability, and adherence to strict project-specific rules such as mandatory 'this' keyword usage and specific member prefixes.
Use Cases
- Automated Code Auditing: Use the skill to evaluate new code submissions against the MotoRent standards, identifying deviations in naming conventions (like the m_ prefix) or improper file organization.
- Modernizing Legacy Codebases: Guide the refactoring of older C# code to implement modern patterns such as collection expressions, primary constructors, and advanced pattern matching as defined in the project guidelines.
- Consistent Service Implementation: Ensure that all new services correctly implement the Service Injection Pattern and follow the required asynchronous I/O practices and error handling protocols.
- Developer Onboarding: Act as an interactive style guide for new team members to quickly learn the specific 'this' keyword rules and entity patterns used within the MotoRent ecosystem.
| name | code-standards |
|---|---|
| description | C# coding conventions, naming patterns, and file organization standards for the MotoRent project. |
Code Standards
C# Coding Standards
- Framework: .NET 10
- Language Version: C# 14 or latest
- Nullable Reference Types: Enabled
- Pattern: Use modern C# features (pattern matching, records, init-only properties)
- Async/Await: Prefer async methods for I/O operations
- Naming Conventions:
- PascalCase for classes, methods, properties
- camelCase for local variables and parameters
- Prefix interfaces with
I - Prefix for private instance members is
m_ - Prefix for static fields is
m_ - Prefix for constants UPPERCASE_WITH_UNDERSCORES
Service Injection Pattern
// In ServicesExtensions.cs
builder.Services.AddScoped<IMotorbikeService, MotorbikeService>();
services.AddSingleton<IRepository<Motorbike>, SqlJsonRepository<Motorbike>>();
// use constructor injection
// ALWAYS use "this" keyword when referencing any instance member of the current class
// ALWAYS use "base" keyword when referencing any base class member
public class MotorbikeService(RentalDataContext context) : IMotorbikeService
{
// use private get property
private RentalDataContext Context {get;} = context;
public async Task DoSomethingAsync(int id)
{
// do NOT omit `this` keyword
var motorbike = await this.Context.LoadOneAsync<Motorbike>(m => m.MotorbikeId == id);
// the rest of the code
if (SelectedShopId > 0 && rc.ShopId != SelectedShopId) // WRONG
if (this.SelectedShopId > 0 && rc.ShopId != this.SelectedShopId) // CORRECT
}
}
Pattern Matching
var boolVar = isTrue ? "yes" : "no";
// for a simple boolean, but when isTrue is complex expression, use pattern
var result = someValue switch
{
> 0 => "positive",
< 0 => "negative",
_ => "zero"
};
File Organization
// 1. Usings (sorted, no unnecessary)
using System.Text.Json;
using MotoRent.Domain.Entities;
// 2. Namespace
namespace MotoRent.Services;
// 3. Type declaration
public class RentalService(RentalDataContext context)
{
// 4. Constants
private const int MAX_RENTAL_DAYS = 30;
// 5. Static fields
private static readonly JsonSerializerOptions s_options = new();
// private properties for injection
private RentalDataContext {get;} = context;
// 6. Instance fields (m_ prefix)
private List<Rental> m_cachedRentals = [];
// 8. Properties
public int ShopId { get; set; }
// 9. Public methods
public async Task<Rental?> GetRentalAsync(int id)
{
return await this.DataContext.LoadOneAsync<Rental>(r => r.RentalId == id);
}
// 9. One line methods
public Task<Rental?> GetRentalAsync(int id) => this.DataContext.LoadOneAsync<Rental>(r => r.RentalId == id);
// 11. Private methods
private void ValidateRental(Rental rental)
{
// ...
}
// DO NOT user #region and #endregion, instead use partial class for example:
// Rental.search.cs for search related members
// Rental.validation.cs for validation rules etc
}
Expression-Bodied Members
// Properties
public int RentalId { get; set; }
public string FullName => $"{this.FirstName} {this.LastName}";
// Methods (single expression)
public override int GetId() => this.RentalId;
public override void SetId(int value) => this.RentalId = value;
// Methods (multiple statements - use block body)
public async Task SaveAsync()
{
using var session = this.m_context.OpenSession();
session.Attach(this);
await session.SubmitChanges("Save");
}
Null Handling
// Nullable reference types (enabled in project)
public string? OptionalField { get; set; }
public string RequiredField { get; set; } = string.Empty;
// Null checks
if (rental is null)
return;
// Pattern matching
if (rental is { Status: "Active" })
ProcessActive(rental);
if (shop == null || !shop.IsActive) // Wrong
if( shop is {IsActive:true}) // correct
// Null coalescing
var name = rental?.RenterName ?? "Unknown";
// Null-forgiving (only when certain)
var item = list.FirstOrDefault()!;
Collections
// Use collection expressions
private List<Rental> Rentals {get;} = [];
private Dictionary<int, Rental> Cache {get;} = [];
// LINQ patterns
var query = this.DataContext.CreateQuery<Rental>()
.Where(r => r.Status == "Active")
.OrderByDescending(r => r.StartDate);
var lo = await this.DataContext.LoadAsync(query);
var rentals = lo.ItemCollection;
// Prefer foreach for side effects
foreach (var rental in rentals)
{
rental.Status = "Completed";
}
Async/Await
// Always use async suffix
public async Task<Rental?> LoadRentalAsync(int id)
// Always await or return
public async Task ProcessAsync()
{
// other statements
await this.DoWorkAsync();
}
// Fire and forget (rare, use with caution)
_ = Task.Run(async () => await this.BackgroundWorkAsync());
// Parallel operations
await Task.WhenAll(
this.LoadRentalsAsync(),
this.LoadMotorbikesAsync(),
this.LoadRentersAsync()
);
Entity Patterns
public class Rental : Entity
{
// Primary key
public int RentalId { get; set; }
// Foreign keys
public int ShopId { get; set; }
public int RenterId { get; set; }
public int MotorbikeId { get; set; }
// Required strings
public string Status { get; set; } = "Reserved";
// Optional strings
public string? Notes { get; set; }
// Dates
public DateTimeOffset StartDate { get; set; }
public DateTimeOffset? ActualEndDate { get; set; }
// Money
public decimal DailyRate { get; set; }
public decimal TotalAmount { get; set; }
// Entity base implementation
public override int GetId() => this.RentalId;
public override void SetId(int value) => this.RentalId = value;
}
Error Handling
// Use try-catch for expected errors
try
{
await this.ProcessRentalAsync(rental);
}
catch (ValidationException ex)
{
this.ToastService.ShowWarning(ex.Message);
}
catch (Exception ex)
{
this.Logger.LogError(ex, "Failed to process rental {RentalId}", rental.RentalId);
this.ToastService.ShowError("An error occurred");
}
// Throw for programming errors
if (rental is null)
throw new ArgumentNullException(nameof(rental));
Comments
// Single-line for brief explanations
// Calculate total including insurance
/// <summary>
/// XML doc for public APIs
/// </summary>
/// <param name="rentalId">The rental identifier</param>
/// <returns>The rental or null if not found</returns>
public async Task<Rental?> GetRentalAsync(int rentalId)
// Avoid obvious comments
// BAD: Increment counter
// GOOD: (no comment needed for i++)
The this Keyword Rule (IMPORTANT)
Always use this when referencing instance members:
// WRONG - missing this keyword
m_loading = true;
DataContext.LoadAsync(query);
ShowSuccess("Saved");
// CORRECT - always use this
this.m_loading = true;
this.DataContext.LoadAsync(query);
this.ShowSuccess("Saved");
This applies to:
- Private fields (
this.m_field) - Properties (
this.PropertyName) - Methods (
this.MethodName()) - Injected services (
this.DataContext,this.DialogService)
Why?
- Clearer distinction between local variables and instance members
- Prevents accidental shadowing
- Consistent with rx-erp codebase patterns
- Easier to identify dependencies in code
Source
- Based on:
E:\project\work\rx-erppatterns - Microsoft C# Coding Conventions
Blazor & Razor files
Use blazor development skill and css styling skill