C# Best Practices
Learn industry-standard practices and patterns to write clean, maintainable, and efficient C# code.
All Best Practices
Coding Standards
9 practicesChoose descriptive names for variables, methods, and classes that clearly express their purpose.
// Good public class CustomerOrderProcessor { private readonly IPaymentService _paymentService; public async Task<OrderResult> ProcessCustomerOrderAsync(Order order) { // Implementation } }
// Bad public class COP { private readonly IPS ps; public async Task<OR> ProcessAsync(O o) { // Implementation } }
Use PascalCase for public members, camelCase for private fields, and consistent naming patterns.
// Good public class ProductService { private readonly ILogger _logger; private const int MaxRetryAttempts = 3; public async Task<Product> GetProductByIdAsync(int productId) { // Implementation } }
// Bad public class productservice { private readonly ILogger Logger; private const int max_retry_attempts = 3; public async Task<Product> getproductbyid(int ProductID) { // Implementation } }
Maintain consistent indentation throughout your code to improve readability and maintainability.
// Good public class Example { public void Method() { if (condition) { // Do something } } }
// Bad public class Example { public void Method() { if (condition) { // Do something } } }
Keep line length to a reasonable limit (e.g., 80-100 characters) to enhance code readability.
// Good public class Example { public void Method() { var longString = "This is a long string that is split " + "across multiple lines for better readability."; } }
// Bad public class Example { public void Method() { var longString = "This is a long string that is not split across multiple lines, making it hard to read."; } }
Use named constants instead of magic numbers to make your code more understandable and maintainable.
// Good public class Circle { private const double Pi = 3.14159; public double CalculateCircumference(double radius) { return 2 * Pi * radius; } }
// Bad public class Circle { public double CalculateCircumference(double radius) { return 2 * 3.14159 * radius; } }
Write comments to explain why code exists, not what it does. Keep comments up-to-date.
// Good public class DataProcessor { // This method processes data from the input stream and applies necessary transformations. public void ProcessData(Stream inputStream) { // Implementation } }
// Bad public class DataProcessor { // Process data public void ProcessData(Stream inputStream) { // Implementation } }
Encapsulate complex conditionals in methods to improve readability and maintainability.
// Good public class Order { public bool IsEligibleForDiscount(Customer customer) { return customer.IsLoyal && customer.TotalOrders > 10; } }
// Bad public class Order { public bool IsEligibleForDiscount(Customer customer) { return customer.IsLoyal && customer.TotalOrders > 10 && customer.HasCoupon; } }
Implement proper exception handling to manage errors gracefully and maintain application stability.
// Good public class FileProcessor { public void ProcessFile(string filePath) { try { // File processing logic } catch (IOException ex) { // Handle I/O exceptions } } }
// Bad public class FileProcessor { public void ProcessFile(string filePath) { // File processing logic without exception handling } }
Use a consistent logging framework to log errors with sufficient context to aid in debugging.
// Good public class UserService { private readonly ILogger<UserService> _logger; public UserService(ILogger<UserService> logger) { _logger = logger; } public void CreateUser(User user) { try { // User creation logic } catch (Exception ex) { _logger.LogError(ex, "Error creating user {UserId}", user.Id); } } }
// Bad public class UserService { public void CreateUser(User user) { try { // User creation logic } catch (Exception) { Console.WriteLine("An error occurred."); } } }
Performance
8 practicesWhen concatenating multiple strings, especially in loops, use StringBuilder for better performance.
// Good var sb = new StringBuilder(); foreach (var item in items) { sb.AppendLine($"Item: {item.Name}"); } string result = sb.ToString();
// Bad string result = ""; foreach (var item in items) { result += $"Item: {item.Name}\n"; }
Use asynchronous programming for I/O bound operations to improve scalability.
// Good public async Task<string> GetDataAsync() { using var client = new HttpClient(); return await client.GetStringAsync("https://api.example.com/data"); }
// Bad public string GetData() { using var client = new HttpClient(); return client.GetStringAsync("https://api.example.com/data").Result; }
Use efficient LINQ queries to minimize performance overhead and improve execution speed.
// Good var result = data.Where(x => x.IsActive).Select(x => x.Name).ToList(); // Use method syntax for better performance var result = (from x in data where x.IsActive select x.Name).ToList();
// Bad var result = data.Select(x => x.Name).Where(x => x.IsActive).ToList();
Utilize parallel processing to perform tasks concurrently and improve application throughput.
// Good Parallel.ForEach(data, item => { ProcessItem(item); });
// Bad foreach (var item in data) { ProcessItem(item); // Sequential processing }
Choose the right data structures for your needs to improve performance and memory usage.
// Good List<int> numbers = new List<int>(); for (int i = 0; i < 1000; i++) { numbers.Add(i); }
// Bad ArrayList numbers = new ArrayList(); for (int i = 0; i < 1000; i++) { numbers.Add(i); }
Avoid unnecessary boxing and unboxing to reduce overhead and improve performance.
// Good int number = 123; object obj = number; // Boxing int unboxedNumber = (int)obj; // Unboxing // Better int number = 123; int sameNumber = number;
// Bad int number = 123; object obj = number; // Boxing int unboxedNumber = (int)obj; // Unboxing
Implement caching to store frequently accessed data and reduce computation time.
// Good public class DataService { private readonly IMemoryCache _cache; public DataService(IMemoryCache cache) { _cache = cache; } public string GetData(string key) { if (!_cache.TryGetValue(key, out string data)) { data = FetchDataFromSource(key); _cache.Set(key, data); } return data; } }
// Bad public class DataService { public string GetData(string key) { return FetchDataFromSource(key); // No caching } }
Reuse objects instead of creating new ones to reduce memory usage and improve performance.
// Good public class ConnectionManager { private readonly SqlConnection _connection = new SqlConnection(); public SqlConnection GetConnection() { return _connection; } }
// Bad public class ConnectionManager { public SqlConnection GetConnection() { return new SqlConnection(); // Creates a new connection every time } }
Security
15 practicesAlways validate input parameters to prevent security vulnerabilities and unexpected behavior.
// Good public void ProcessUser(string email, int age) { if (string.IsNullOrWhiteSpace(email)) throw new ArgumentException("Email cannot be null or empty", nameof(email)); if (age < 0 || age > 150) throw new ArgumentOutOfRangeException(nameof(age), "Age must be between 0 and 150"); // Process user }
// Bad public void ProcessUser(string email, int age) { // No validation - potential security risk var user = new User { Email = email, Age = age }; // Process user }
Ensure all data transmitted between the client and server is encrypted using HTTPS.
// Good public void Configure(IApplicationBuilder app) { app.UseHttpsRedirection(); // Other middleware }
// Bad public void Configure(IApplicationBuilder app) { // No HTTPS redirection // Other middleware }
Use authentication and authorization to protect resources and ensure only authorized users can access them.
// Good services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.Authority = "https://your-auth-server.com"; options.Audience = "your-audience"; }); services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); });
// Bad // No authentication or authorization setup services.AddAuthorization();
Always sanitize user input to prevent injection attacks such as SQL injection and cross-site scripting (XSS).
// Good public void ExecuteQuery(string userInput) { var sanitizedInput = Sanitize(userInput); // Use sanitizedInput in database query }
// Bad public void ExecuteQuery(string userInput) { // Directly using userInput in database query }
Store passwords securely using strong hashing algorithms like bcrypt.
// Good public void StorePassword(string password) { var hashedPassword = BCrypt.Net.BCrypt.HashPassword(password); // Store hashedPassword in database }
// Bad public void StorePassword(string password) { // Storing plain text password in database }
Keep all libraries and dependencies up to date to protect against known vulnerabilities.
// Good // Regularly check for updates and apply them // Use tools like Dependabot or npm audit to automate this process
// Bad // Ignoring dependency updates // Using outdated libraries with known vulnerabilities
Use rate limiting to protect your application from abuse and denial-of-service attacks.
// Good public void Configure(IApplicationBuilder app) { app.UseRateLimiting(); // Other middleware }
// Bad public void Configure(IApplicationBuilder app) { // No rate limiting // Other middleware }
Implement CSP to prevent cross-site scripting (XSS) and other code injection attacks.
// Good public void Configure(IApplicationBuilder app) { app.UseCsp(options => options .DefaultSources(s => s.Self()) .ScriptSources(s => s.Self().CustomSources("https://trustedscripts.example.com")) .StyleSources(s => s.Self().UnsafeInline()) ); // Other middleware }
// Bad public void Configure(IApplicationBuilder app) { // No CSP implementation // Other middleware }
Ensure cookies are secure by setting the Secure and HttpOnly flags.
// Good var cookieOptions = new CookieOptions { Secure = true, // Ensures the cookie is sent over HTTPS only HttpOnly = true // Prevents JavaScript access to the cookie }; Response.Cookies.Append("SessionId", sessionId, cookieOptions);
// Bad var cookieOptions = new CookieOptions { // No Secure or HttpOnly flags }; Response.Cookies.Append("SessionId", sessionId, cookieOptions);
Add security headers like X-Content-Type-Options, X-Frame-Options, and X-XSS-Protection to enhance security.
// Good public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); context.Response.Headers.Add("X-Frame-Options", "DENY"); context.Response.Headers.Add("X-XSS-Protection", "1; mode=block"); await next(); }); // Other middleware }
// Bad public void Configure(IApplicationBuilder app) { // No security headers // Other middleware }
Ensure that strong encryption algorithms are used for data protection.
// Good public void EncryptData(string data) { using (Aes aes = Aes.Create()) { aes.Key = GenerateStrongKey(); // Encryption logic } }
// Bad public void EncryptData(string data) { using (Aes aes = Aes.Create()) { aes.Key = GenerateWeakKey(); // Encryption logic } }
Ensure that access controls are in place to restrict access to sensitive data and operations.
// Good public void ConfigureServices(IServiceCollection services) { services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); }); }
// Bad // No access control policies defined services.AddAuthorization();
Regularly monitor and audit logs to detect and respond to security incidents.
// Good public void Configure(IApplicationBuilder app, ILogger<Startup> logger) { app.Use(async (context, next) => { logger.LogInformation("Request: {Method} {Path}", context.Request.Method, context.Request.Path); await next(); }); // Other middleware }
// Bad public void Configure(IApplicationBuilder app) { // No logging or monitoring // Other middleware }
Perform regular security testing, including vulnerability scans and penetration testing, to identify and address security weaknesses.
// Good // Schedule regular security assessments // Use tools like OWASP ZAP or Nessus for automated scans
// Bad // No regular security testing // Ignoring potential vulnerabilities
Provide regular security training to employees to raise awareness and reduce the risk of human error.
// Good // Conduct regular security workshops and training sessions // Keep employees informed about the latest security threats
// Bad // No security training for employees // Lack of awareness about security best practices
Architecture
13 practicesImplement dependency injection to create loosely coupled, testable code.
// Good public class OrderService { private readonly IPaymentProcessor _paymentProcessor; private readonly IEmailService _emailService; public OrderService(IPaymentProcessor paymentProcessor, IEmailService emailService) { _paymentProcessor = paymentProcessor; _emailService = emailService; } }
// Bad public class OrderService { private readonly PaymentProcessor _paymentProcessor = new PaymentProcessor(); private readonly EmailService _emailService = new EmailService(); }
Design your application as a collection of loosely coupled services to improve scalability and maintainability.
// Good public class OrderService { public void ProcessOrder(Order order) { // Logic to process order } } // Microservice setup public class OrderMicroservice { private readonly OrderService _orderService; public OrderMicroservice(OrderService orderService) { _orderService = orderService; } public void Start() { // Start microservice } }
// Bad public class MonolithicOrderProcessor { public void ProcessOrder(Order order) { // Logic to process order // All logic in one place } }
Implement event-driven architecture to decouple components and improve responsiveness.
// Good public class EventPublisher { public void PublishEvent(Event event) { // Logic to publish event } } public class EventSubscriber { public void Subscribe() { // Logic to subscribe to events } }
// Bad public class TightlyCoupledComponent { public void Execute() { // Direct calls to other components } }
Use DDD to align your software model with business needs and improve communication between technical and non-technical stakeholders.
// Good public class CustomerAggregate { public void AddOrder(Order order) { // Logic to add order to customer } }
// Bad public class Customer { public void AddOrder(Order order) { // Logic scattered across multiple classes } }
Structure your application into layers (e.g., presentation, business, data) to separate concerns and improve maintainability.
// Good public class PresentationLayer { public void DisplayData() { // Logic to display data } } public class BusinessLayer { public void ProcessData() { // Logic to process data } }
// Bad public class MonolithicApplication { public void Execute() { // All logic in one place } }
Separate the read and write operations of your application to optimize performance and scalability.
// Good public class CommandHandler { public void HandleCommand(Command command) { // Logic to handle command } } public class QueryHandler { public TResult HandleQuery<TResult>(Query query) { // Logic to handle query return default(TResult); } }
// Bad public class UnifiedHandler { public void HandleRequest(Request request) { // Mixed logic for handling commands and queries } }
Implement an API Gateway to manage and route requests to various microservices, providing a single entry point.
// Good public class ApiGateway { public void RouteRequest(Request request) { // Logic to route request to appropriate microservice } }
// Bad public class DirectClientAccess { public void AccessService(Service service) { // Direct access to microservices without a gateway } }
Use semantic versioning (MAJOR.MINOR.PATCH) to communicate changes in your API or software effectively.
// Good // Version 1.0.0 - Initial release // Version 1.1.0 - Added new features // Version 2.0.0 - Breaking changes introduced
// Bad // Version 1 - Initial release // Version 2 - Added new features and breaking changes
Ensure your APIs are versioned to manage changes and maintain backward compatibility.
// Good // API v1 [Route("api/v1/products")] public IActionResult GetProductsV1() { // Logic for version 1 } // API v2 [Route("api/v2/products")] public IActionResult GetProductsV2() { // Logic for version 2 }
// Bad // No versioning [Route("api/products")] public IActionResult GetProducts() { // Logic without versioning }
Maintain a changelog to document changes, bug fixes, and updates for each version.
// Good // Changelog // Version 1.0.0 - Initial release // Version 1.1.0 - Added feature X // Version 1.1.1 - Fixed bug Y
// Bad // No changelog // Users are unaware of changes and updates
Provide clear deprecation notices and timelines for phasing out old versions.
// Good // Deprecation notice // API v1 will be deprecated on 2023-12-31 // Please migrate to API v2
// Bad // No deprecation notice // Users are caught off guard by sudden changes
Use feature toggles to manage the release of new features without deploying a new version.
// Good public class FeatureToggle { public bool IsFeatureXEnabled { get; set; } public void ToggleFeatureX() { // Logic to enable/disable feature X } }
// Bad // No feature toggles // New features require a full deployment
Adopt branching strategies like Git Flow or trunk-based development to manage version control effectively.
// Good // Git Flow // - master: production-ready code // - develop: latest development changes // - feature branches: new features
// Bad // No branching strategy // Code changes are made directly to the main branch
Additional Resources
Deepen your understanding with these comprehensive guides