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
