← C# EnglishChapter 13 of 13

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 →