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

3
Code Examples
2
Design Patterns
4
Best Practices
4
Common Issues

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.