Skip to main content

csharp-patterns

C#/.NET: LINQ, async/await, DI, records, nullable refs, ASP.NET Core, EF Core, MediatR. Triggers: C#, .NET, dotnet, ASP.NET, EF Core, LINQ, record type, IServiceCollection.

Quellinformationen

Repository
softspark/ai-toolkit
Letzte Quellaktivität
12. Mai 2026 um 13:02
Erkannte Sprache von SKILL.md
Englisch
Sterne
179
Forks
21

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
csharp-patterns
description
C#/.NET: LINQ, async/await, DI, records, nullable refs, ASP.NET Core, EF Core, MediatR. Triggers: C#, .NET, dotnet, ASP.NET, EF Core, LINQ, record type, IServiceCollection.
effort
medium
user-invocable
false
allowed-tools
Read
# C# / .NET Patterns ## Project Structure ### Solution Layout ``` MyApp.sln Directory.Build.props # Shared build properties Directory.Packages.props # Central package management src/ MyApp.Api/ # ASP.NET Core host (Controllers, Middleware, Program.cs) MyApp.Application/ # Use cases, MediatR handlers, Behaviors MyApp.Domain/ # Entities, value objects, domain events MyApp.Infrastructure/ # EF Core, external services tests/ MyApp.UnitTests/ MyApp.IntegrationTests/ ``` ### Directory.Build.props ```xml <Project> <PropertyGroup> <TargetFramework>net9.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> <AnalysisLevel>latest-recommended</AnalysisLevel> </PropertyGroup> </Project> ``` ### Central Package Management (Directory.Packages.props) ```xml <Project> <PropertyGroup> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> </PropertyGroup> <ItemGroup> <PackageVersion Include="MediatR" Version="12.4.1" /> <PackageVersion Include="FluentValidation" Version="11.11.0" /> <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.0" /> </ItemGroup> </Project> ``` --- ## Idioms / Code Style ### Nullable Reference Types ```csharp public class Order { public required string Id { get; init; } public string? Notes { get; set; } // explicitly nullable public required Customer Customer { get; init; } // never null public string Summary => $"Order {Id}: {Notes ?? "no notes"}"; } ``` ### Records ```csharp public record Money(decimal Amount, string Currency) { public Money Add(Money other) => Currency != other.Currency ? throw new InvalidOperationException("Currency mismatch") : this with { Amount = Amount + other.Amount }; } public record CreateOrderRequest(string CustomerId, List<OrderLineDto> Lines); public record OrderLineDto(string ProductId, int Quantity); ``` ### Pattern Matching ```csharp public decimal CalculateDiscount(Customer c) => c switch { { Tier: CustomerTier.Gold, TotalSpent: > 10_000m } => 0.20m, { Tier: CustomerTier.Gold } => 0.15m, { Tier: CustomerTier.Silver } => 0.10m, { IsNewCustomer: true } => 0.05m, _ => 0m }; // List patterns (.NET 8+) public string Describe(int[] v) => v switch { [] => "empty", [var x] => $"one: {x}", [var f, .., var l] => $"{f}..{l}", }; ``` ### LINQ ```csharp var activeUsers = users .Where(u => u.IsActive) .OrderByDescending(u => u.LastLogin) .Select(u => new UserDto(u.Id, u.Name)) .ToList(); ``` ### Async/Await ```csharp // Always: Async suffix, CancellationToken parameter, never .Result/.Wait() public async Task<Order?> GetOrderAsync(string id, CancellationToken ct = default) => await _db.Orders.Include(o => o.Lines).FirstOrDefaultAsync(o => o.Id == id, ct); // Parallel independent tasks var ordersTask = _orderRepo.GetRecentAsync(ct); var statsTask = _statsService.ComputeAsync(ct); await Task.WhenAll(ordersTask, statsTask); ``` ### Primary Constructors (C# 12) ```csharp public class OrderService(IOrderRepository repo, ILogger<OrderService> logger, IPublisher pub) { public async Task<Order> CreateAsync(CreateOrderRequest req, CancellationToken ct) { logger.LogInformation("Creating order for {CustomerId}", req.CustomerId); var order = Order.Create(req); await repo.AddAsync(order, ct); await pub.Publish(new OrderCreatedEvent(order.Id), ct); return order; } } ``` --- ## Error Handling ### Result Pattern ```csharp public sealed class Result<T> { public T? Value { get; } public Error? Error { get; } public bool IsSuccess => Error is null; private Result(T value) => Value = value; private Result(Error error) => Error = error; public static Result<T> Success(T value) => new(value); public static Result<T> Failure(Error error) => new(error); public TOut Match<TOut>(Func<T, TOut> ok, Func<Error, TOut> err) => IsSuccess ? ok(Value!) : err(Error!); } public record Error(string Code, string Message); ``` ### FluentValidation + MediatR Pipeline ```csharp public class CreateOrderValidator : AbstractValidator<CreateOrderRequest> { public CreateOrderValidator() { RuleFor(x => x.CustomerId).NotEmpty().MaximumLength(36); RuleForEach(x => x.Lines).ChildRules(line => { line.RuleFor(l => l.ProductId).NotEmpty(); line.RuleFor(l => l.Quantity).GreaterThan(0).LessThanOrEqualTo(1000); }); } } public class ValidationBehavior<TReq, TRes>(IEnumerable<IValidator<TReq>> validators) : IPipelineBehavior<TReq, TRes> where TReq : IRequest<TRes> { public async Task<TRes> Handle(TReq req, RequestHandlerDelegate<TRes> next, CancellationToken ct) { var failures = validators.Select(v => v.Validate(req)).SelectMany(r => r.Errors).ToList(); return failures.Count > 0 ? throw new ValidationException(failures) : await next(); } } ``` ### IAsyncDisposable ```csharp public sealed class TempFileHandle(string path) : IAsyncDisposable { public async ValueTask DisposeAsync() { if (File.Exists(path)) await Task.Run(() => File.Delete(path)); } } // Usage: await using var handle = new TempFileHandle("/tmp/export.csv"); ``` --- ## Testing Patterns ### xUnit + NSubstitute + FluentAssertions ```csharp public class OrderServiceTests { private readonly IOrderRepository _repo = Substitute.For<IOrderRepository>(); private readonly IPublisher _pub = Substitute.For<IPublisher>(); private readonly OrderService _sut; public OrderServiceTests() => _sut = new(_repo, Substitute.For<ILogger<OrderService>>(), _pub); [Fact] public async Task CreateAsync_ValidRequest_ReturnsOrder() { var order = await _sut.CreateAsync(new("cust-1", [new("prod-1", 2)]), CancellationToken.None); order.Should().NotBeNull(); order.Lines.Should().ContainSingle().Which.Quantity.Should().Be(2); await _repo.Received(1).AddAsync(Arg.Any<Order>(), Arg.Any<CancellationToken>()); } [Theory] [InlineData(0)] [InlineData(-1)] public async Task CreateAsync_InvalidQuantity_Throws(int qty) { var act = () => _sut.CreateAsync(new("cust-1", [new("prod-1", qty)]), CancellationToken.None); await act.Should().ThrowAsync<ValidationException>(); } } ``` ### WebApplicationFactory (Integration) ```csharp public class OrdersApiTests(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>> { [Fact] public async Task PostOrder_Returns201() { var client = factory.WithWebHostBuilder(b => b.ConfigureServices(s => { s.RemoveAll<DbContext>(); s.AddDbContext<AppDbContext>(o => o.UseInMemoryDatabase("test")); })).CreateClient(); var response = await client.PostAsJsonAsync("/api/orders", new { CustomerId = "cust-1", Lines = new[] { new { ProductId = "prod-1", Quantity = 2 } } }); response.StatusCode.Should().Be(HttpStatusCode.Created); } } ``` ### Testcontainers ```csharp public class PostgresFixture : IAsyncLifetime { private readonly PostgreSqlContainer _pg = new PostgreSqlBuilder().WithDatabase("testdb").Build(); public string ConnectionString => _pg.GetConnectionString(); public Task InitializeAsync() => _pg.StartAsync(); public Task DisposeAsync() => _pg.DisposeAsync().AsTask(); } ``` --- ## Common Frameworks ### ASP.NET Core Minimal API ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)); var app = builder.Build(); app.MapGet("/api/orders/{id}", async (string id, ISender sender, CancellationToken ct) => await sender.Send(new GetOrderQuery(id), ct) is { } order ? Results.Ok(order) : Results.NotFound()); app.MapPost("/api/orders", async (CreateOrderRequest req, ISender sender, CancellationToken ct) => (await sender.Send(new CreateOrderCommand(req), ct)).Match( o => Results.Created($"/api/orders/{o.Id}", o), e => Results.Problem(e.Message, statusCode: 400))); app.Run(); ``` ### Entity Framework Core ```csharp public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options) { public DbSet<Order> Orders => Set<Order>(); protected override void OnModelCreating(ModelBuilder mb) { mb.Entity<Order>(b => { b.HasKey(o => o.Id); b.Property(o => o.Id).HasMaxLength(36); b.HasMany(o => o.Lines).WithOne().HasForeignKey(l => l.OrderId); b.HasIndex(o => o.CustomerId); b.Property(o => o.Total).HasPrecision(18, 2); }); mb.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly); } } ``` ### MediatR (CQRS) ```csharp public record CreateOrderCommand(CreateOrderRequest Request) : IRequest<Result<Order>>; public class CreateOrderHandler(IOrderRepository repo, IPublisher pub) : IRequestHandler<CreateOrderCommand, Result<Order>> { public async Task<Result<Order>> Handle(CreateOrderCommand cmd, CancellationToken ct) { var order = Order.Create(cmd.Request); await repo.AddAsync(order, ct); await pub.Publish(new OrderCreatedEvent(order.Id), ct); return Result<Order>.Success(order); } } ``` ### Polly (Resilience) ```csharp builder.Services.AddHttpClient("Payments", c => c.BaseAddress = new Uri("https://api.payments.com")) .AddResilienceHandler("default", p => { p.AddRetry(new() { MaxRetryAttempts = 3, Delay = TimeSpan.FromMilliseconds(500), BackoffType = DelayBackoffType.Exponential, ShouldHandle = new PredicateBuilder<HttpResponseMessage>() .HandleResult(r => r.StatusCode >= HttpStatusCode.InternalServerError) }); p.AddCircuitBreaker(new() { FailureRatio = 0.5, SamplingDuration = TimeSpan.FromSeconds(30), BreakDuration = TimeSpan.FromSeconds(15) }); p.AddTimeout(TimeSpan.FromSeconds(5)); }); ``` ### MassTransit (Messaging) ```csharp builder.Services.AddMassTransit(x => { x.AddConsumer<OrderCreatedConsumer>(); x.UsingRabbitMq((ctx, cfg) => { cfg.Host("rabbitmq://localhost"); cfg.ConfigureEndpoints(ctx); }); }); ``` --- ## Performance Tips ### Span<T> -- Zero-Allocation Parsing ```csharp public static ReadOnlySpan<char> ExtractDomain(ReadOnlySpan<char> email) { int at = email.IndexOf('@'); return at >= 0 ? email[(at + 1)..] : ReadOnlySpan<char>.Empty; } // Stack-allocated buffer: Span<byte> buf = stackalloc byte[256]; ``` ### ValueTask -- Cache-Hit Fast Path ```csharp public ValueTask<Product?> GetProductAsync(string id, CancellationToken ct) {
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen