C# & .NET
Modern C# development, .NET patterns och best practices
Vad du hittar här
Moderna C# och .NET utvecklingsmönster, från dependency injection till avancerade design patterns. Innehåller praktiska exempel och lösningar på vanliga problem.
Innehåll
C# Code Examples
Dependency Injection Setup
Configure dependency injection container in .NET applications.
// Program.cs (.NET 6+)
var builder = WebApplication.CreateBuilder(args);
// Register services
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddSingleton<ICacheService, MemoryCacheService>();
builder.Services.AddTransient<IEmailService, EmailService>();
// Add Entity Framework
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
💡 Förklaring: Registers services with different lifetimes: Scoped (per request), Singleton (app lifetime), Transient (each time)
Async/Await Pattern
Proper async/await implementation for database operations.
public async Task<User> GetUserAsync(int userId)
{
try
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
var sql = "SELECT * FROM Users WHERE Id = @UserId";
var user = await connection.QueryFirstOrDefaultAsync<User>(sql,
new { UserId = userId });
return user;
}
catch (SqlException ex)
{
_logger.LogError(ex, "Database error while fetching user {UserId}", userId);
throw;
}
}
💡 Förklaring: Uses async/await with proper exception handling and resource disposal
Configuration Pattern
Strongly-typed configuration using IOptions pattern.
// appsettings.json
{
"EmailSettings": {
"SmtpHost": "smtp.example.com",
"SmtpPort": 587,
"Username": "user@example.com",
"EnableSsl": true
}
}
// EmailSettings.cs
public class EmailSettings
{
public string SmtpHost { get; set; }
public int SmtpPort { get; set; }
public string Username { get; set; }
public bool EnableSsl { get; set; }
}
// Program.cs
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection("EmailSettings"));
// Usage in service
public class EmailService
{
private readonly EmailSettings _emailSettings;
public EmailService(IOptions<EmailSettings> emailSettings)
{
_emailSettings = emailSettings.Value;
}
}
💡 Förklaring: Binds configuration sections to strongly-typed classes for better maintainability
Design Patterns
Repository Pattern
Abstracts data access logic and provides a consistent interface.
public interface IUserRepository
{
Task<User> GetByIdAsync(int id);
Task<IEnumerable<User>> GetAllAsync();
Task<User> CreateAsync(User user);
Task UpdateAsync(User user);
Task DeleteAsync(int id);
}
public class UserRepository : IUserRepository
{
private readonly ApplicationDbContext _context;
public UserRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<User> GetByIdAsync(int id)
{
return await _context.Users
.FirstOrDefaultAsync(u => u.Id == id);
}
public async Task<User> CreateAsync(User user)
{
_context.Users.Add(user);
await _context.SaveChangesAsync();
return user;
}
}
Fördelar:
- • Testability
- • Separation of concerns
- • Consistent data access API
Unit of Work Pattern
Manages transactions and coordinates multiple repository operations.
public interface IUnitOfWork : IDisposable
{
IUserRepository Users { get; }
IOrderRepository Orders { get; }
Task<int> SaveChangesAsync();
Task BeginTransactionAsync();
Task CommitTransactionAsync();
Task RollbackTransactionAsync();
}
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _context;
private IDbContextTransaction _transaction;
public UnitOfWork(ApplicationDbContext context)
{
_context = context;
Users = new UserRepository(context);
Orders = new OrderRepository(context);
}
public IUserRepository Users { get; }
public IOrderRepository Orders { get; }
public async Task<int> SaveChangesAsync()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_transaction?.Dispose();
_context.Dispose();
}
}
Fördelar:
- • Transaction management
- • Consistency across repositories
- • Resource cleanup
Best Practices
✅ Use Nullable Reference Types
Enable nullable reference types to catch null reference exceptions at compile time
💡 Exempel: <Nullable>enable</Nullable> in project file and use string? for nullable strings
✅ Implement Proper Logging
Use structured logging with appropriate log levels and contexts
💡 Exempel: _logger.LogInformation("User {UserId} performed action {Action}", userId, action)
✅ Follow SOLID Principles
Design classes with single responsibility and dependency inversion
💡 Exempel: Use interfaces for dependencies and keep classes focused on one responsibility
✅ Use Configuration Validation
Validate configuration values at startup to fail fast
💡 Exempel: Use IValidateOptions<T> to validate configuration objects during registration
Performance Optimization
🚀 Database Connection Pooling
Optimize database connections by using connection pooling properly
🔧 Lösning: Use connection strings with appropriate pooling settings and avoid holding connections too long
🚀 Async All The Way
Use async/await throughout the call chain to avoid thread pool starvation
🔧 Lösning: Never mix async and sync code (avoid .Result or .Wait())
🚀 Memory Management
Minimize allocations and implement IDisposable for resources
🔧 Lösning: Use object pooling, span/memory for large data, and proper disposal patterns
🚀 Caching Strategies
Implement appropriate caching for frequently accessed data
🔧 Lösning: Use IMemoryCache for local caching and distributed cache for scalable solutions
Vanliga problem och lösningar
⚠️ NullReferenceException
Most common runtime exception in C# applications
🔧 Lösning: Enable nullable reference types and use null-conditional operators (?. and ??)
⚠️ Memory Leaks
Objects not being garbage collected due to event handlers or static references
🔧 Lösning: Properly unsubscribe from events and avoid keeping references in static collections
⚠️ Deadlocks in Async Code
Mixing synchronous and asynchronous code can cause deadlocks
🔧 Lösning: Use ConfigureAwait(false) in library code and avoid .Result/.Wait()
⚠️ Entity Framework N+1 Problem
Executing multiple queries when one would suffice
🔧 Lösning: Use Include() for related data or projection with Select() to avoid lazy loading issues
💡C# Development Tips
Använd Code Analysis
Aktivera kod-analys och behandla warnings som errors för att fånga problem tidigt.
Skriv enhetstester
Använd xUnit eller NUnit för att skriva enhetstester och säkerställa kodkvalitet.
Följ .NET conventions
Använd PascalCase för publika medlemmar och camelCase för privata fält.