Best Practices
## Learning Objectives
- Write clean, maintainable C# code
- Understand garbage collection
- Introduction to async programming
- Follow naming conventions
## Naming Conventions
### PascalCase
```csharp
// Classes, methods, properties, events
public class BankAccount
{
public string AccountNumber { get; set; }
public decimal Balance { get; set; }
public event EventHandler? BalanceChanged;
public void Deposit(decimal amount) { }
}
```
### camelCase
```csharp
// Local variables, parameters
public void CalculateTotal(int firstNumber, int secondNumber)
{
decimal totalAmount = firstNumber + secondNumber;
}
```
### Private Fields
```csharp
class Person
{
private string _name; // Underscore prefix (recommended)
private int _age;
// Or
private string name; // No prefix
private int age;
}
```
### Constants
```csharp
// const - PascalCase
public const int MaxRetryCount = 3;
public const string DefaultCulture = "en-US";
// readonly - _camelCase
private readonly ILogger _logger;
```
## Code Organization
### Namespace Organization
```csharp
namespace MyApp.Services
{
public class OrderService { }
}
namespace MyApp.Models
{
public class Order { }
}
namespace MyApp.Repositories
{
public class OrderRepository { }
}
```
### File Organization
```csharp
// One public class per file (recommended)
// File name matches class name
// Order.cs contains Order class
```
### Using Directives
```csharp
// System namespaces first
using System;
using System.Collections.Generic;
using System.Linq;
// Then third-party
using Newtonsoft.Json;
// Then project
using MyApp.Models;
using MyApp.Services;
```
### Global Using (C# 10+)
```csharp
// GlobalUsings.cs
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;
```
## Garbage Collection
### How GC Works
```csharp
// Objects without references are eligible for collection
class Program
{
static void Main()
{
var person = new Person("Alice");
person = null; // Now eligible for GC
GC.Collect(); // Force collection (not recommended)
GC.WaitForPendingFinalizers();
}
}
```
### IDisposable Pattern
```csharp
class ResourceHolder : IDisposable
{
private bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Release managed resources
}
// Release unmanaged resources
_disposed = true;
}
}
~ResourceHolder() // Finalizer
{
Dispose(false);
}
}
// Usage
using (var holder = new ResourceHolder())
{
// Use holder
} // Automatically disposed
```
### using Statement
```csharp
// Ensure disposal
using (var reader = new StreamReader("file.txt"))
using (var writer = new StreamWriter("output.txt"))
{
// Use resources
} // Both disposed
// C# 8+ simplified
using var reader = new StreamReader("file.txt");
string content = reader.ReadToEnd();
// Disposed at end of scope
```
## Async Programming
### async and await
```csharp
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("Starting...");
await LongRunningOperation();
Console.WriteLine("Done!");
}
static async Task LongRunningOperation()
{
await Task.Delay(1000); // Simulate work
Console.WriteLine("Operation complete");
}
}
```
### Returning Values
```csharp
static async Task CalculateSumAsync(int a, int b)
{
await Task.Delay(100); // Simulate async work
return a + b;
}
// Usage
int result = await CalculateSumAsync(5, 3);
Console.WriteLine(result); // 8
```
### Async vs Synchronous
```csharp
// Synchronous (blocks)
string content = File.ReadAllText("file.txt");
// Asynchronous (non-blocking)
async Task ReadFileAsync()
{
return await File.ReadAllTextAsync("file.txt");
}
// Usage
string content = await ReadFileAsync();
```
### Don't Use async void
```csharp
// BAD - fire and forget, exceptions can't be caught
async void BadMethod()
{
await Task.Delay(100);
throw new Exception("Oops!");
}
// GOOD - async Task
async Task GoodMethodAsync()
{
await Task.Delay(100);
throw new Exception("Oops!");
}
```
## Null Safety
### Nullable Reference Types (C# 8+)
```csharp
// Non-nullable by default (in nullable context)
string name = "Alice"; // Cannot be null
// name = null; // Warning!
string? nullable = null; // Can be null
// Null check required before use
if (nullable != null)
{
Console.WriteLine(nullable.Length);
}
// Null-conditional operator
int? length = nullable?.Length;
// Null-coalescing
string safe = nullable ?? "default";
```
### Patterns for Null
```csharp
// Pattern matching with null
if (obj is string s && s.Length > 0)
{
Console.WriteLine(s);
}
// switch expression with null
string Describe(object? obj) => obj switch
{
null => "null",
string s => $"String: {s}",
int i => $"Int: {i}",
_ => "Something else"
};
```
## Performance Tips
### String Building
```csharp
// Bad - creates many strings
string result = "";
for (int i = 0; i < 100; i++)
{
result += i.ToString();
}
// Good - StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++)
{
sb.Append(i);
}
string result = sb.ToString();
```
### List Capacity
```csharp
// If you know the size
List numbers = new List(100); // Pre-allocate
for (int i = 0; i < 100; i++)
{
numbers.Add(i);
}
```
### LINQ Performance
```csharp
// Deferred execution
var query = collection.Where(x => x > 5); // Not executed yet
// Forced execution when needed
var list = query.ToList(); // Executes
// For large datasets, consider
// - ToList() early to avoid multiple enumeration
// - AsParallel() for CPU-intensive queries
```
## Error Handling Best Practices
```csharp
// Specific exceptions first
try
{
await ReadDataAsync();
}
catch (FileNotFoundException ex)
{
// Handle specific
}
catch (IOException ex)
{
// Handle IO
}
catch (Exception ex)
{
// Log unexpected errors
Logger.Error(ex);
throw;
}
// Exception filters for granular control
try
{
Process(data);
}
catch (Exception ex) when (ex is InvalidOperationException or ArgumentException)
{
// Handle specific exceptions
}
```
## Code Quality
### SOLID Principles
```csharp
// Single Responsibility
class UserService { void CreateUser() { } }
class EmailService { void SendEmail() { } }
// Open/Closed - extend, don't modify
abstract class Shape { public abstract double Area(); }
class Rectangle : Shape { public double Width, Height; public override double Area() => Width * Height; }
// Liskov Substitution
// Derived classes must be usable as base class
// Interface Segregation
interface IReadable { void Read(); }
interface IWritable { void Write(); }
// Dependency Inversion
class OrderService
{
private readonly IRepository _repository;
public OrderService(IRepository repository) => _repository = repository;
}
```
### Useful Attributes
```csharp
// Common attributes
[Obsolete("Use NewMethod instead")]
void OldMethod() { }
[DebuggerDisplay("Name = {Name}")]
class Person { public string Name { get; set; } }
[Serializable]
class Data { }
// Nullable
#nullable enable
class MyClass
{
public string? NullableProperty { get; set; }
}
#nullable restore
```
## Summary
- Follow naming conventions (PascalCase, camelCase)
- Use global using (C# 10+) for clean files
- Implement IDisposable for unmanaged resources
- Prefer async/await for I/O operations
- Enable nullable reference types
- Use StringBuilder for string concatenation
- Handle exceptions from specific to general
- Follow SOLID principles
- Write self-documenting code with clear names
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →