Rules: csharp
// 2. Private readonly fields private readonly IUserRepository _userRepository; private readonly ILogger<UserService> _logger;
Affected files
These rules apply to files matching the following patterns:
**/*.cs**/*.csproj**/*.sln
Detailed rules
C# Rules
Code Conventions
Naming
| Element | Convention | Example |
|---|---|---|
| Classes | PascalCase | UserService |
| Interfaces | PascalCase with I prefix | IUserRepository |
| Methods | PascalCase | GetUserById |
| Properties | PascalCase | FirstName |
| Local variables | camelCase | userName |
| Parameters | camelCase | userId |
| Constants | PascalCase | MaxRetryCount |
| Private fields | _camelCase | _userRepository |
Class Structure
public class UserService : IUserService
{
// 1. Constants
private const int MaxRetries = 3;
// 2. Private readonly fields
private readonly IUserRepository _userRepository;
private readonly ILogger<UserService> _logger;
// 3. Constructor
public UserService(IUserRepository userRepository, ILogger<UserService> logger)
{
_userRepository = userRepository;
_logger = logger;
}
// 4. Properties
public int RetryCount { get; private set; }
// 5. Public methods
public async Task<User?> GetByIdAsync(int id) { ... }
// 6. Private methods
private void ValidateUser(User user) { ... }
}
Best Practices
Nullable reference types
// Enable in the project
<Nullable>enable</Nullable>
// Use correctly
public User? FindById(int id) // May return null
public User GetById(int id) // Never returns null
// Pattern matching
if (user is { Name: var name, Email: var email })
{
Console.WriteLine($"{name}: {email}");
}
Records (C# 9+)
// Immutable record
public record User(int Id, string Name, string Email);
// Record with mutable properties if needed
public record User
{
public int Id { get; init; }
public string Name { get; init; } = "";
public string Email { get; init; } = "";
}
Async/Await
// Always suffix with Async
public async Task<User> GetUserAsync(int id)
{
return await _repository.FindByIdAsync(id)
?? throw new UserNotFoundException(id);
}
// Avoid .Result and .Wait()
// Use ConfigureAwait(false) in libraries
public async Task<User> GetUserAsync(int id)
{
return await _repository.FindByIdAsync(id).ConfigureAwait(false);
}
LINQ
// Fluent syntax preferred
var activeUsers = users
.Where(u => u.IsActive)
.OrderBy(u => u.Name)
.Select(u => new UserDto(u.Id, u.Name))
.ToList();
// Use Any() instead of Count() > 0
if (users.Any(u => u.IsAdmin))
{
// ...
}
Pattern Matching
// Switch expressions (C# 8+)
var message = status switch
{
Status.Active => "User is active",
Status.Pending => "User is pending",
Status.Inactive => "User is inactive",
_ => throw new ArgumentOutOfRangeException(nameof(status))
};
// Pattern matching with types
if (result is Success<User> { Value: var user })
{
return user;
}
ASP.NET Core
Controllers
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<UserDto>> GetById(int id)
{
var user = await _userService.GetByIdAsync(id);
return user is null ? NotFound(): Ok(user);
}
[HttpPost]
[ProducesResponseType(typeof(UserDto), StatusCodes.Status201Created)]
public async Task<ActionResult<UserDto>> Create([FromBody] CreateUserRequest request)
{
var user = await _userService.CreateAsync(request);
return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
}
}
Dependency Injection
// Program.cs
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IUserService, UserService>();
// Options pattern
builder.Services.Configure<EmailOptions>(
builder.Configuration.GetSection("Email"));
Validation
public class CreateUserRequest
{
[Required]
[StringLength(100, MinimumLength = 2)]
public string Name { get; init; } = "";
[Required]
[EmailAddress]
public string Email { get; init; } = "";
[Required]
[MinLength(8)]
public string Password { get; init; } = "";
}
Tests
xUnit + FluentAssertions
public class UserServiceTests
{
private readonly Mock<IUserRepository> _repositoryMock;
private readonly UserService _sut;
public UserServiceTests()
{
_repositoryMock = new Mock<IUserRepository>();
_sut = new UserService(_repositoryMock.Object);
}
[Fact]
public async Task GetByIdAsync_WhenUserExists_ReturnsUser()
{
// Arrange
var user = new User(1, "John", "john@example.com");
_repositoryMock
.Setup(r => r.FindByIdAsync(1))
.ReturnsAsync(user);
// Act
var result = await _sut.GetByIdAsync(1);
// Assert
result.Should().NotBeNull();
result!.Name.Should().Be("John");
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Validate_WithInvalidName_ThrowsException(string? name)
{
// Act & Assert
var act = () => _sut.Validate(new User(1, name!, "email@test.com"));
act.Should().Throw<ValidationException>();
}
}
To Avoid
dynamicexcept in very specific casesgoto- Exceptions for flow control
- Public fields (use properties)
async voidexcept for event handlers
Automatic application
These rules are automatically applied by Claude during:
- Reading the matching files
- Modifying code
- Suggestions and fixes