.NET Coding Standards
Introduction
Coding standards are a set of guidelines, best practices, and programming rules established to create consistent, readable, and maintainable code. For .NET developers, following standardized coding practices is essential, especially when working in teams or on projects that may be maintained by different developers over time.
In this guide, we'll explore the essential .NET coding standards that every beginner should know. These standards apply to all .NET languages, but we'll focus primarily on C# examples as it's the most commonly used language in the .NET ecosystem.
Why Are Coding Standards Important?
Before diving into specific standards, let's understand why coding standards matter:
- Readability: Standards make code easier to read and understand
- Maintainability: Consistent code is easier to maintain and modify
- Onboarding: New team members can understand and contribute to the codebase faster
- Fewer Bugs: Following standards can prevent common coding mistakes
- Efficiency: Standards can lead to more efficient code reviews and collaborations
Naming Conventions
Pascal Case and Camel Case
In .NET, two primary casing conventions are used:
- PascalCase: First letter of each word is capitalized (e.g.,
EmployeeService
) - camelCase: First letter is lowercase, but first letter of each subsequent word is capitalized (e.g.,
employeeCount
)
When to Use Each Convention
Here's a table outlining when to use each naming convention:
Element | Convention | Example |
---|---|---|
Classes | PascalCase | CustomerOrder |
Interfaces | PascalCase with "I" prefix | IDisposable |
Methods | PascalCase | CalculateTotal |
Properties | PascalCase | FirstName |
Private Fields | camelCase with underscore prefix | _totalAmount |
Parameters | camelCase | employeeId |
Local Variables | camelCase | orderCount |
Constants | PascalCase | MaximumRetryCount |
Example of Proper Naming Convention
public class CustomerOrder
{
private readonly int _maxRetryCount = 3;
private double _totalAmount;
public string OrderId { get; set; }
public DateTime OrderDate { get; set; }
public double CalculateTotal(double taxRate, bool includeShipping)
{
int itemCount = GetItemCount();
_totalAmount = /* calculation logic */;
return _totalAmount;
}
}
Code Formatting and Layout
Indentation and Braces
.NET code typically follows these formatting guidelines:
- Use 4 spaces for indentation (not tabs)
- Place opening braces on a new line
- Align closing braces with the opening statement
// Good practice
public void ProcessOrder()
{
if (IsValid)
{
// Process the order
}
}
// Avoid
public void ProcessOrder() {
if (IsValid) {
// Process the order
}
}
Line Length and Wrapping
- Keep lines reasonably short (typically under 100-120 characters)
- When wrapping method parameters, indent them once
// Good practice
public void SendNotification(
string recipient,
string subject,
string message,
NotificationPriority priority)
{
// Method implementation
}
Comments and Documentation
XML Documentation Comments
For public APIs and classes, use XML documentation comments. These comments can be used to generate documentation automatically:
/// <summary>
/// Calculates the total price including applicable taxes.
/// </summary>
/// <param name="price">Base price before tax</param>
/// <param name="taxRate">Tax rate as a decimal (e.g., 0.08 for 8%)</param>
/// <returns>The total price including tax</returns>
public decimal CalculateTotalPrice(decimal price, decimal taxRate)
{
return price * (1 + taxRate);
}
Regular Comments
For internal implementation details, use regular comments:
// Calculate the adjusted price based on seasonal discount
var adjustedPrice = originalPrice * (1 - seasonalDiscountRate);
Code Organization
File Organization
- One class per file (with exceptions for small related classes)
- Name the file the same as the primary class it contains
- Group related classes in appropriate folders/namespaces
Class Organization
Structure your classes in this typical order:
- Constants
- Fields
- Constructors
- Properties
- Methods
- Nested types
public class Product
{
// Constants
public const int MaxNameLength = 50;
// Fields
private readonly string _sku;
private decimal _price;
// Constructor
public Product(string sku, string name)
{
_sku = sku;
Name = name;
}
// Properties
public string Name { get; set; }
public decimal Price
{
get => _price;
set => _price = value >= 0 ? value : throw new ArgumentException("Price cannot be negative");
}
// Methods
public decimal CalculateDiscount(decimal percentage)
{
return Price * percentage / 100;
}
// Nested types
public enum ProductCategory
{
Electronics,
Clothing,
Food
}
}
Error Handling
Exception Handling Best Practices
- Catch specific exceptions rather than generic ones
- Don't catch exceptions you can't handle properly
- Include meaningful information in exception messages
public void SaveCustomerData(Customer customer)
{
try
{
// Try to save customer data to database
_repository.Save(customer);
}
catch (DbConnectionException ex)
{
// Log the specific error
_logger.LogError($"Database connection failed: {ex.Message}");
throw new ServiceException("Unable to save customer data due to connection issues", ex);
}
catch (DbConcurrencyException ex)
{
// Handle concurrency conflict
_logger.LogWarning($"Concurrency conflict detected: {ex.Message}");
throw new ConflictException("Another user has already updated this customer", ex);
}
}
SOLID Principles in .NET Code
Example: Single Responsibility Principle
Each class should have only one reason to change:
// Bad practice: One class doing too much
public class Customer
{
public void SaveToDatabase() { /* ... */ }
public void GenerateInvoice() { /* ... */ }
public void SendEmailNotification() { /* ... */ }
}
// Good practice: Separate responsibilities
public class Customer
{
// Only customer data and behavior
}
public class CustomerRepository
{
public void Save(Customer customer) { /* ... */ }
}
public class InvoiceService
{
public Invoice GenerateInvoice(Customer customer) { /* ... */ }
}
public class NotificationService
{
public void SendEmail(Customer customer, string message) { /* ... */ }
}
Practical Example: Building a Customer Order System
Let's apply these standards to a real-world scenario:
// File: Order.cs
using System;
using System.Collections.Generic;
namespace ECommerce.OrderManagement
{
/// <summary>
/// Represents a customer order in the system.
/// </summary>
public class Order
{
private readonly IOrderValidator _validator;
private readonly List<OrderItem> _items = new List<OrderItem>();
private const decimal MinimumOrderValue = 10.00m;
/// <summary>
/// Creates a new order for the specified customer.
/// </summary>
/// <param name="customerId">The unique identifier of the customer</param>
/// <param name="validator">The validator to use for order validation</param>
public Order(int customerId, IOrderValidator validator)
{
if (customerId <= 0)
{
throw new ArgumentException("Customer ID must be positive", nameof(customerId));
}
CustomerId = customerId;
OrderDate = DateTime.Now;
_validator = validator ?? throw new ArgumentNullException(nameof(validator));
}
/// <summary>
/// Gets or sets the unique identifier for this order.
/// </summary>
public int OrderId { get; set; }
/// <summary>
/// Gets the customer identifier associated with this order.
/// </summary>
public int CustomerId { get; }
/// <summary>
/// Gets the date when the order was created.
/// </summary>
public DateTime OrderDate { get; }
/// <summary>
/// Gets the status of the order.
/// </summary>
public OrderStatus Status { get; private set; } = OrderStatus.Pending;
/// <summary>
/// Gets the read-only collection of items in this order.
/// </summary>
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
/// <summary>
/// Adds a new item to the order.
/// </summary>
/// <param name="productId">The product identifier</param>
/// <param name="quantity">The quantity to order</param>
/// <param name="unitPrice">The unit price of the product</param>
/// <returns>True if the item was added successfully; otherwise, false</returns>
public bool AddItem(int productId, int quantity, decimal unitPrice)
{
try
{
var newItem = new OrderItem(productId, quantity, unitPrice);
_items.Add(newItem);
return true;
}
catch (ArgumentException ex)
{
// Log the error
Console.WriteLine($"Failed to add item: {ex.Message}");
return false;
}
}
/// <summary>
/// Calculates the total value of all items in the order.
/// </summary>
/// <returns>The total order value</returns>
public decimal CalculateTotalValue()
{
decimal total = 0;
foreach (var item in _items)
{
total += item.CalculateSubtotal();
}
return total;
}
/// <summary>
/// Validates and submits the order for processing.
/// </summary>
/// <returns>True if the order was submitted successfully; otherwise, false</returns>
public bool SubmitOrder()
{
// Check if order meets minimum value requirement
if (CalculateTotalValue() < MinimumOrderValue)
{
return false;
}
// Validate order using injected validator
if (!_validator.ValidateOrder(this))
{
return false;
}
Status = OrderStatus.Submitted;
return true;
}
/// <summary>
/// Represents the possible statuses for an order.
/// </summary>
public enum OrderStatus
{
Pending,
Submitted,
Processing,
Shipped,
Delivered,
Canceled
}
}
}
// File: OrderItem.cs
using System;
namespace ECommerce.OrderManagement
{
/// <summary>
/// Represents an individual item within an order.
/// </summary>
public class OrderItem
{
/// <summary>
/// Creates a new order item.
/// </summary>
/// <param name="productId">The product identifier</param>
/// <param name="quantity">The quantity ordered</param>
/// <param name="unitPrice">The price per unit</param>
public OrderItem(int productId, int quantity, decimal unitPrice)
{
if (productId <= 0)
{
throw new ArgumentException("Product ID must be positive", nameof(productId));
}
if (quantity <= 0)
{
throw new ArgumentException("Quantity must be positive", nameof(quantity));
}
if (unitPrice < 0)
{
throw new ArgumentException("Unit price cannot be negative", nameof(unitPrice));
}
ProductId = productId;
Quantity = quantity;
UnitPrice = unitPrice;
}
/// <summary>
/// Gets the product identifier.
/// </summary>
public int ProductId { get; }
/// <summary>
/// Gets or sets the quantity ordered.
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// Gets or sets the price per unit.
/// </summary>
public decimal UnitPrice { get; set; }
/// <summary>
/// Calculates the subtotal for this item.
/// </summary>
/// <returns>The subtotal (quantity × unit price)</returns>
public decimal CalculateSubtotal()
{
return Quantity * UnitPrice;
}
}
}
// File: IOrderValidator.cs
namespace ECommerce.OrderManagement
{
/// <summary>
/// Defines methods for validating orders.
/// </summary>
public interface IOrderValidator
{
/// <summary>
/// Validates an order according to business rules.
/// </summary>
/// <param name="order">The order to validate</param>
/// <returns>True if the order is valid; otherwise, false</returns>
bool ValidateOrder(Order order);
}
}
Summary
Following .NET coding standards is essential for creating clean, maintainable, and professional code. The key points to remember include:
- Consistency: Follow naming conventions and formatting consistently throughout your codebase.
- Readability: Write code as if someone else will read it (because they will).
- Documentation: Document public APIs and add helpful comments to clarify complex logic.
- Organization: Structure your code logically, with clear separation of concerns.
- Error Handling: Handle exceptions properly and provide meaningful error messages.
- SOLID Principles: Apply software design principles to create modular, maintainable code.
By adhering to these standards, you'll not only write better code but also become a more effective team member in any .NET development project.
Additional Resources
- Microsoft's C# Coding Conventions
- .NET API Design Guidelines
- StyleCop: A C# style and consistency analyzer
- Book: "Clean Code: A Handbook of Agile Software Craftsmanship" by Robert C. Martin
Exercises
- Code Review Exercise: Take a sample of poorly formatted code and refactor it to follow the .NET coding standards.
- Naming Convention Practice: Write a class with fields, properties, and methods following proper naming conventions.
- XML Documentation: Add complete XML documentation to an existing class to generate meaningful documentation.
- SOLID Refactoring: Identify a class that violates the Single Responsibility Principle and refactor it into multiple classes.
- Exception Handling: Implement proper exception handling in a method that performs file operations or database access.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)