| name | c-sharp |
| description | [Applies to: **/*] This guide defines definitive C# coding standards and best practices for our team, covering naming, formatting, modern language features, performance, security, and error handling to ensure consistent, maintainable, and high-quality code. |
| source | cursor_mdc |
c-sharp Best Practices
This document outlines the definitive C# coding standards and best practices for our team. Adhering to these guidelines ensures consistency, readability, maintainability, and high performance across all our C# projects.
Code Organization and Structure
1. Naming Conventions
Always follow standard .NET naming conventions to improve readability and predictability.
- Types (Classes, Structs, Enums, Delegates, Records):
PascalCase
- Interfaces:
IPascalCase
- Methods, Public Properties, Events:
PascalCase
- Public Constants:
PascalCase
- Local Variables, Method Parameters:
camelCase
- Private/Protected Fields:
_camelCase (underscore prefix)
- Async Methods:
PascalCase ending with Async
❌ BAD:
public class myService {
private string name;
public void getdata() { }
public async Task do_work() { }
public interface IMyServiceImplementation { }
}
✅ GOOD:
public class MyService {
private readonly ILogger _logger;
private string _name;
public const int MaxRetries = 3;
public MyService(ILogger logger) {
_logger = logger;
}
public void GetData() { }
public async Task DoWorkAsync() { }
}
public interface IService { }
2. Formatting
Always maintain consistent code layout for clarity.
- Indentation: Use 4 spaces. Configure your IDE to use spaces, not tabs.
- Braces: Use Allman style where each brace begins on a new line. Always use braces, even for single-statement blocks.
using Directives: Place at the top of the file, outside the namespace. Sort alphabetically, with System namespaces first.
- Auto-properties: Keep on a single line.
- Generic Constraints: Place on separate, indented lines.
❌ BAD:
using System.Collections.Generic;
using System;
namespace MyNamespace {
class MyClass {
public int Age {get;set;}
void DoSomething() {
if(true)
Console.WriteLine("Hello");
}
}}
✅ GOOD:
using System;
using System.Collections.Generic;
namespace MyNamespace
{
public class MyClass
{
public int Age { get; set; }
public void DoSomething()
{
if (true)
{
Console.WriteLine("Hello");
}
}
}
}
3. Modern Language Features
Always embrace modern C# features for cleaner, more concise, and safer code.
var Keyword: Use var when the type is obvious from the right-hand side of the assignment. Avoid var when the type is not immediately clear.
- Expression-bodied Members: Use for single-expression methods, properties, and accessors.
- Object/Collection Initializers: Prefer for creating and populating objects/collections.
- Pattern Matching: Use for type checks and value comparisons.
- Nullable Reference Types: Enable and address warnings (
#nullable enable).
- Records: Use for immutable data transfer objects (DTOs) or value-based types.
❌ BAD:
string name = "John Doe";
public int GetCount() { return _items.Count; }
MyObject obj = new MyObject();
obj.Property1 = "Value1";
obj.Property2 = 123;
if (item is string) { Console.WriteLine((string)item); }
✅ GOOD:
var name = "John Doe";
public int Count => _items.Count;
public void Log(string message) => _logger.LogInformation(message);
var obj = new MyObject {
Property1 = "Value1",
Property2 = 123
};
if (item is string s) { Console.WriteLine(s); }
Common Patterns and Anti-patterns
1. Dependency Injection (DI)
Always use constructor injection for dependencies. Never use service locators or direct instantiation of dependencies within classes.
❌ BAD:
public class MyService
{
private readonly IRepository _repository = new ConcreteRepository();
}
✅ GOOD:
public class MyService
{
private readonly IRepository _repository;
public MyService(IRepository repository)
{
_repository = repository;
}
}
2. Immutability
Favor immutable objects, especially for DTOs and shared state, to reduce side effects and improve thread safety. Use record types or init accessors.
❌ BAD:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
✅ GOOD:
public record User(string FirstName, string LastName);
public class Product
{
public int Id { get; init; }
public string Name { get; init; }
}
Performance Considerations
1. Asynchronous Programming
Always use async/await for I/O-bound operations to prevent blocking threads. Avoid async void methods (except for event handlers) and blocking async calls.
❌ BAD:
public void DoWork() {
SomeAsyncOperation().Wait();
}
public async void Event_Click(object sender, EventArgs e) { }
✅ GOOD:
public async Task DoWorkAsync() {
await SomeAsyncOperation();
}
public async void Button_Click(object sender, EventArgs e) {
await LoadDataAsync();
}
2. String Manipulation
Always use StringBuilder for concatenating many strings in loops.
❌ BAD:
string result = "";
for (int i = 0; i < 1000; i++) {
result += i.ToString();
}
✅ GOOD:
var sb = new System.Text.StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.Append(i);
}
string result = sb.ToString();
Common Pitfalls and Gotchas
1. Magic Strings/Numbers
Never hardcode string literals or numeric values directly in code. Always use constants or configuration.
❌ BAD:
public void ProcessOrder(string status) {
if (status == "Pending") { }
}
public decimal CalculateDiscount(decimal price) {
return price * 0.10m;
}
✅ GOOD:
public static class OrderStatus {
public const string Pending = "Pending";
public const string Completed = "Completed";
}
public const decimal DiscountRate = 0.10m;
public void ProcessOrder(string status) {
if (status == OrderStatus.Pending) { }
}
public decimal CalculateDiscount(decimal price) {
return price * DiscountRate;
}
2. Deep Nesting
Always refactor deeply nested if/for blocks to improve readability. Use guard clauses or extract methods.
❌ BAD:
if (condition1) {
if (condition2) {
if (condition3) {
}
}
}
✅ GOOD:
if (!condition1) return;
if (!condition2) return;
if (!condition3) return;
Security Best Practices
1. Input Validation
Always validate all external input (user input, API calls, file contents) at the boundaries of your application.
❌ BAD:
public void CreateUser(string username, string password) {
_userRepository.Add(new User(username, password));
}
✅ GOOD:
public void CreateUser(string username, string password) {
if (string.IsNullOrWhiteSpace(username) || username.Length < 5) {
throw new ArgumentException("Invalid username.");
}
_userRepository.Add(new User(username, password));
}
2. Secure Data Handling
Never store sensitive information (passwords, API keys) in plain text. Always use secure configuration, environment variables, or secrets management solutions.
Error Handling
1. Specific Exceptions
Always catch specific exceptions rather than general Exception. Log and rethrow when appropriate.
❌ BAD:
try {
} catch (Exception ex) {
_logger.LogError(ex, "An error occurred.");
throw;
}
✅ GOOD:
try {
} catch (FileNotFoundException ex) {
_logger.LogError(ex, "File not found at path {Path}.", _filePath);
} catch (IOException ex) {
_logger.LogError(ex, "An I/O error occurred during file operation.");
throw;
}
2. using for IDisposable
Always use the using statement for objects that implement IDisposable to ensure proper resource cleanup.
❌ BAD:
SqlConnection connection = new SqlConnection("...");
connection.Open();
✅ GOOD:
using (var connection = new SqlConnection("..."))
{
connection.Open();
}
API Design
1. Clear Contracts
Always define clear, well-documented API contracts using DTOs and interfaces. Avoid exposing internal implementation details.
2. Asynchronous APIs
Always expose async methods for any I/O-bound operations in public APIs.
❌ BAD:
public IEnumerable<Product> GetProducts() { }
✅ GOOD:
public Task<IEnumerable<Product>> GetProductsAsync() { }
Testing Approaches
1. Unit Testing
Always write focused unit tests for individual components, mocking external dependencies.
2. Dependency Injection for Testability
Always design classes with Dependency Injection to make them easily testable.
❌ BAD:
public class OrderProcessor {
private readonly PaymentGateway _gateway = new PaymentGateway();
public void Process(Order order) { _gateway.Charge(order.Amount); }
}
✅ GOOD:
public interface IPaymentGateway { void Charge(decimal amount); }
public class PaymentGateway : IPaymentGateway { }
public class OrderProcessor {
private readonly IPaymentGateway _gateway;
public OrderProcessor(IPaymentGateway gateway) { _gateway = gateway; }
public void Process(Order order) { _gateway.Charge(order.Amount); }
}