Summary
C# is a versatile, object-oriented programming language developed by Microsoft for building robust backend applications. This cheatsheet covers essential C# concepts for backend development including fundamentals, OOP principles, collections, async programming, ASP.NET Core, Entity Framework, design patterns, and best practices. Key topics include dependency injection, LINQ, Web API development, performance optimization, security, and testing strategies.
C# Fundamentals
Value Types vs Reference Types
// Value Types (stored on stack)
int x = 5;
int y = x; // y gets copy of x
x = 10; // y is still 5
// Reference Types (stored on heap)
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1; // both point to same object
list1.Add(4); // list2 also has 4 elements
Nullable Types
int? nullableInt = null;
int value = nullableInt ?? 0; // null coalescing
int? result = nullableInt?.CompareTo(5); // null conditional
String Operations
// String interpolation
string name = "John";
string message = $"Hello, {name}!";
// StringBuilder for performance
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
sb.Append(i);
Generics
public class Repository<T> where T : class
{
public T GetById(int id) { /* implementation */ }
}
// Multiple constraints
public interface IService<T> where T : class, new()
{
T Create();
}
Object-Oriented Programming
Inheritance & Polymorphism
public abstract class Animal
{
public abstract void MakeSound();
public virtual void Sleep() => Console.WriteLine("Sleeping");
}
public class Dog : Animal
{
public override void MakeSound() => Console.WriteLine("Woof");
public override void Sleep() => Console.WriteLine("Dog sleeping");
}
Interfaces
public interface IRepository<T>
{
T GetById(int id);
Task<T> GetByIdAsync(int id);
}
// Multiple interface implementation
public class UserRepository : IRepository<User>, IDisposable
{
public User GetById(int id) { /* ... */ }
public async Task<User> GetByIdAsync(int id) { /* ... */ }
public void Dispose() { /* ... */ }
}
Properties & Encapsulation
public class Person
{
// Auto-property with init-only setter (C# 9)
public string Name { get; init; }
// Backing field
private int _age;
public int Age
{
get => _age;
set => _age = value >= 0 ? value : 0;
}
// Expression-bodied property
public string Info => $"{Name}, {Age} years old";
}
Collections & LINQ
Common Collections
// List
List<int> numbers = new() { 1, 2, 3 };
// Dictionary
Dictionary<string, int> ages = new()
{
["John"] = 25,
["Jane"] = 30
};
// HashSet (unique values)
HashSet<int> uniqueNumbers = new() { 1, 2, 3, 3 }; // Contains 1,2,3
// Queue & Stack
Queue<string> queue = new();
queue.Enqueue("first");
string first = queue.Dequeue();
Stack<int> stack = new();
stack.Push(1);
int top = stack.Pop();
LINQ Queries
var users = new List<User>();
// Query syntax
var adults = from u in users
where u.Age >= 18
orderby u.Name
select u;
// Method syntax (preferred)
var activeUsers = users
.Where(u => u.IsActive)
.OrderBy(u => u.CreatedAt)
.Take(10)
.ToList();
// Grouping
var usersByCountry = users
.GroupBy(u => u.Country)
.Select(g => new { Country = g.Key, Count = g.Count() });
// Join
var userOrders = users.Join(orders,
u => u.Id,
o => o.UserId,
(u, o) => new { u.Name, o.Total });
Async Programming
async/await Pattern
public async Task<string> GetDataAsync()
{
using var client = new HttpClient();
var response = await client.GetAsync("https://api.example.com");
return await response.Content.ReadAsStringAsync();
}
// Parallel async operations
public async Task<(string, string)> GetMultipleDataAsync()
{
var task1 = GetDataAsync();
var task2 = GetDataAsync();
var results = await Task.WhenAll(task1, task2);
return (results[0], results[1]);
}
ConfigureAwait
// In library code, avoid capturing context
public async Task<T> GetAsync<T>()
{
var data = await GetDataAsync().ConfigureAwait(false);
return JsonSerializer.Deserialize<T>(data);
}
CancellationToken
public async Task LongRunningOperation(CancellationToken ct)
{
for (int i = 0; i < 100; i++)
{
ct.ThrowIfCancellationRequested();
await Task.Delay(100, ct);
}
}
Exception Handling
Try-Catch-Finally
try
{
var result = await ProcessDataAsync();
}
catch (ArgumentException ex)
{
// Handle specific exception
_logger.LogError(ex, "Invalid argument");
throw; // Re-throw to preserve stack trace
}
catch (Exception ex)
{
// Handle general exception
_logger.LogError(ex, "Unexpected error");
throw new ServiceException("Processing failed", ex);
}
finally
{
// Cleanup code
}
Custom Exceptions
public class BusinessException : Exception
{
public string ErrorCode { get; }
public BusinessException(string message, string errorCode)
: base(message)
{
ErrorCode = errorCode;
}
}
ASP.NET Core Basics
Program.cs (Minimal API)
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddControllers();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddScoped<IUserService, UserService>();
var app = builder.Build();
// Configure pipeline
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Middleware
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
public LoggingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Before
var start = DateTime.UtcNow;
await _next(context);
// After
var duration = DateTime.UtcNow - start;
Console.WriteLine($"{context.Request.Path}: {duration.TotalMilliseconds}ms");
}
}
Web API Development
Controller Example
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<UserDto>>> GetAll()
{
var users = await _userService.GetAllAsync();
return Ok(users);
}
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetById(int id)
{
var user = await _userService.GetByIdAsync(id);
if (user == null)
return NotFound();
return Ok(user);
}
[HttpPost]
public async Task<ActionResult<UserDto>> Create(CreateUserDto dto)
{
var user = await _userService.CreateAsync(dto);
return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
}
}
Model Validation
public class CreateUserDto
{
[Required]
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Range(18, 120)]
public int Age { get; set; }
}
Action Filters
public class ValidationFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
public void OnActionExecuted(ActionExecutedContext context) { }
}
Entity Framework Core
DbContext Configuration
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options) { }
public DbSet<User> Users { get; set; }
public DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Fluent API configuration
modelBuilder.Entity<User>()
.HasKey(u => u.Id);
modelBuilder.Entity<User>()
.Property(u => u.Email)
.IsRequired()
.HasMaxLength(255);
modelBuilder.Entity<User>()
.HasIndex(u => u.Email)
.IsUnique();
// Relationships
modelBuilder.Entity<Order>()
.HasOne(o => o.User)
.WithMany(u => u.Orders)
.HasForeignKey(o => o.UserId);
}
}
Repository Pattern
public interface IRepository<T> where T : class
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T entity);
void Update(T entity);
void Delete(T entity);
Task SaveChangesAsync();
}
public class Repository<T> : IRepository<T> where T : class
{
protected readonly AppDbContext _context;
protected readonly DbSet<T> _dbSet;
public Repository(AppDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T> GetByIdAsync(int id)
{
return await _dbSet.FindAsync(id);
}
public async Task<IEnumerable<T>> GetAllAsync()
{
return await _dbSet.ToListAsync();
}
public async Task AddAsync(T entity)
{
await _dbSet.AddAsync(entity);
}
public void Update(T entity)
{
_dbSet.Update(entity);
}
public void Delete(T entity)
{
_dbSet.Remove(entity);
}
public async Task SaveChangesAsync()
{
await _context.SaveChangesAsync();
}
}
LINQ with EF Core
// Include related data
var usersWithOrders = await _context.Users
.Include(u => u.Orders)
.ThenInclude(o => o.OrderItems)
.Where(u => u.IsActive)
.ToListAsync();
// Projection
var userDtos = await _context.Users
.Select(u => new UserDto
{
Id = u.Id,
Name = u.Name,
OrderCount = u.Orders.Count()
})
.ToListAsync();
// Raw SQL
var users = await _context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Age > {0}", 18)
.ToListAsync();
Dependency Injection
Service Lifetimes
// Transient - new instance each time
builder.Services.AddTransient<IEmailService, EmailService>();
// Scoped - new instance per request
builder.Services.AddScoped<IUserService, UserService>();
// Singleton - single instance for app lifetime
builder.Services.AddSingleton<ICacheService, CacheService>();
Service Registration
// Interface-based
builder.Services.AddScoped<IUserRepository, UserRepository>();
// Implementation factory
builder.Services.AddScoped<IService>(provider =>
{
var config = provider.GetRequiredService<IConfiguration>();
return new Service(config["ApiKey"]);
});
// Multiple implementations
builder.Services.AddScoped<INotificationService, EmailNotificationService>();
builder.Services.AddScoped<INotificationService, SmsNotificationService>();
// Retrieve all implementations
public class NotificationManager
{
private readonly IEnumerable<INotificationService> _services;
public NotificationManager(IEnumerable<INotificationService> services)
{
_services = services;
}
}
Design Patterns
Singleton Pattern
public sealed class Singleton
{
private static readonly Lazy<Singleton> _instance =
new(() => new Singleton());
public static Singleton Instance => _instance.Value;
private Singleton() { }
}
Factory Pattern
public interface IPaymentProcessor
{
Task<bool> ProcessPayment(decimal amount);
}
public class PaymentProcessorFactory
{
public IPaymentProcessor CreateProcessor(PaymentType type)
{
return type switch
{
PaymentType.CreditCard => new CreditCardProcessor(),
PaymentType.PayPal => new PayPalProcessor(),
_ => throw new NotSupportedException()
};
}
}
Generic Repository Pattern
public interface IUserRepository
{
Task<User> GetByIdAsync(int id);
Task<IEnumerable<User>> GetActiveUsersAsync();
Task AddAsync(User user);
Task UpdateAsync(User user);
}
public class UserRepository : IUserRepository
{
private readonly AppDbContext _context;
public UserRepository(AppDbContext context)
{
_context = context;
}
public async Task<User> GetByIdAsync(int id)
{
return await _context.Users
.Include(u => u.Orders)
.FirstOrDefaultAsync(u => u.Id == id);
}
public async Task<IEnumerable<User>> GetActiveUsersAsync()
{
return await _context.Users
.Where(u => u.IsActive)
.ToListAsync();
}
}
Unit of Work Pattern
public interface IUnitOfWork : IDisposable
{
IUserRepository Users { get; }
IOrderRepository Orders { get; }
Task<int> SaveChangesAsync();
}
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
public UnitOfWork(AppDbContext 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()
{
_context.Dispose();
}
}
Performance & Best Practices
Caching
public class CachedUserService : IUserService
{
private readonly IUserService _userService;
private readonly IMemoryCache _cache;
public CachedUserService(IUserService userService, IMemoryCache cache)
{
_userService = userService;
_cache = cache;
}
public async Task<User> GetByIdAsync(int id)
{
var cacheKey = $"user_{id}";
if (_cache.TryGetValue(cacheKey, out User cachedUser))
return cachedUser;
var user = await _userService.GetByIdAsync(id);
_cache.Set(cacheKey, user, TimeSpan.FromMinutes(5));
return user;
}
}
Async Best Practices
// ❌ Bad - blocking async code
var result = GetDataAsync().Result;
var data = GetDataAsync().GetAwaiter().GetResult();
// ✅ Good - proper async usage
var result = await GetDataAsync();
// ❌ Bad - async void (except event handlers)
public async void ProcessData() { }
// ✅ Good - return Task
public async Task ProcessData() { }
// ✅ Good - fire and forget with proper handling
_ = Task.Run(async () =>
{
try
{
await LongRunningOperation();
}
catch (Exception ex)
{
_logger.LogError(ex, "Background operation failed");
}
});
String Performance
// ❌ Bad - string concatenation in loop
string result = "";
for (int i = 0; i < 1000; i++)
result += i.ToString();
// ✅ Good - StringBuilder
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
sb.Append(i);
var result = sb.ToString();
// ✅ Good - string.Join for collections
var numbers = Enumerable.Range(1, 1000);
var result = string.Join(",", numbers);
Security
Input Validation
public class UserInputValidator
{
public bool ValidateEmail(string email)
{
if (string.IsNullOrWhiteSpace(email))
return false;
return Regex.IsMatch(email,
@"^[^@\s]+@[^@\s]+\.[^@\s]+$",
RegexOptions.IgnoreCase);
}
public string SanitizeInput(string input)
{
// Remove potentially dangerous characters
return Regex.Replace(input, @"[<>""'%;()&+]", "");
}
}
Authentication & Authorization
// JWT Bearer Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
// Authorization policies
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("MinimumAge", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
});
// Controller with authorization
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class SecureController : ControllerBase
{
[HttpGet("admin")]
[Authorize(Policy = "AdminOnly")]
public IActionResult AdminEndpoint()
{
return Ok("Admin access granted");
}
}
Data Protection
public class EncryptionService
{
private readonly IDataProtector _protector;
public EncryptionService(IDataProtectionProvider provider)
{
_protector = provider.CreateProtector("MyApp.Purpose");
}
public string Encrypt(string plainText)
{
return _protector.Protect(plainText);
}
public string Decrypt(string cipherText)
{
return _protector.Unprotect(cipherText);
}
}
Testing
Unit Testing with xUnit
public class UserServiceTests
{
private readonly Mock<IUserRepository> _mockRepo;
private readonly UserService _service;
public UserServiceTests()
{
_mockRepo = new Mock<IUserRepository>();
_service = new UserService(_mockRepo.Object);
}
[Fact]
public async Task GetById_ReturnsUser_WhenUserExists()
{
// Arrange
var expectedUser = new User { Id = 1, Name = "John" };
_mockRepo.Setup(r => r.GetByIdAsync(1))
.ReturnsAsync(expectedUser);
// Act
var result = await _service.GetByIdAsync(1);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedUser.Name, result.Name);
_mockRepo.Verify(r => r.GetByIdAsync(1), Times.Once);
}
[Theory]
[InlineData(0)]
[InlineData(-1)]
public async Task GetById_ThrowsException_WhenIdInvalid(int id)
{
// Assert
await Assert.ThrowsAsync<ArgumentException>(() =>
_service.GetByIdAsync(id));
}
}
Integration Testing
public class UserControllerIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public UserControllerIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task GetUsers_ReturnsSuccessStatusCode()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/api/users");
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal("application/json; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
Test Doubles
// Mocking dependencies
var mockLogger = new Mock<ILogger<UserService>>();
mockLogger.Setup(l => l.LogInformation(It.IsAny<string>()));
// Stubbing data
var stubRepository = new Mock<IUserRepository>();
stubRepository.Setup(r => r.GetAllAsync())
.ReturnsAsync(new List<User>
{
new User { Id = 1 },
new User { Id = 2 }
});
// Verify interactions
mockLogger.Verify(l =>
l.LogError(It.IsAny<Exception>(), It.IsAny<string>()),
Times.Never);
Key Concepts & Best Practices
Memory Management Essentials
| Concept | Description | Example |
|---|---|---|
| Stack | Value types, method parameters | int x = 5 |
| Heap | Reference types, objects | var list = new List<int>() |
| Garbage Collection | Automatic cleanup, Gen 0/1/2 | Managed automatically |
| IDisposable | Unmanaged resources | using var file = new FileStream() |
SOLID Principles Summary
- Single Responsibility: One class, one purpose
- Open/Closed: Extend functionality without modifying existing code
- Liskov Substitution: Derived classes must be substitutable for base classes
- Interface Segregation: Multiple specific interfaces over one large interface
- Dependency Inversion: Depend on abstractions, not implementations
Performance Optimization Strategies
- String Operations: Use
StringBuilderfor concatenation,string.Join()for collections - Async Operations: Use
async/awaitfor I/O, avoid blocking with.Result - Database: Use projections, eager loading with
Include(), pagination - Caching: Implement response caching, in-memory caching for frequently accessed data
- Collections: Use appropriate collection types (
HashSetfor uniqueness,Dictionaryfor lookups)
Common Pitfalls to Avoid
| Pitfall | Problem | Solution |
|---|---|---|
| Resource Leaks | Not disposing IDisposable | Use using statements |
| Blocking Async | .Result or .Wait() |
Use await consistently |
| Null References | Unhandled null values | Use null checks, nullable types |
| Thread Safety | Race conditions | Use locks, concurrent collections |
| Hard-coded Values | Configuration in code | Use appsettings.json, IOptions |
Development Best Practices
✅ Architecture: Use dependency injection, layered architecture
✅ Testing: Write unit tests, integration tests, use mocking
✅ Error Handling: Implement global exception handling, proper logging
✅ Security: Validate input, use authentication/authorization, encrypt sensitive data
✅ Code Quality: Follow naming conventions, keep methods focused, document complex logic
✅ Performance: Use appropriate data structures, implement caching, optimize database queries
Quick Reference
Common Data Types
// Value Types
int, long, float, double, decimal, bool, char, DateTime, Guid
struct MyStruct { }
// Reference Types
string, object, class, interface, delegate, array
List<T>, Dictionary<TKey, TValue>, HashSet<T>
// Nullable Types
int? nullableInt = null;
string? nullableString = null; // C# 8+
Essential Attributes
// Web API
[ApiController], [Route], [HttpGet], [HttpPost]
[FromBody], [FromQuery], [FromRoute], [FromHeader]
// Validation
[Required], [StringLength], [Range], [EmailAddress]
[RegularExpression], [Compare]
// Entity Framework
[Key], [Required], [MaxLength], [Column], [Table]
[ForeignKey], [Index]
HTTP Status Codes Reference
- 200 OK: Success
- 201 Created: Resource created
- 204 No Content: Success, no response body
- 400 Bad Request: Invalid input
- 401 Unauthorized: Authentication required
- 403 Forbidden: Access denied
- 404 Not Found: Resource not found
- 500 Internal Server Error: Server error