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.

来源信息

仓库
softspark/ai-toolkit
最近来源活动
2026年5月12日 13:02
检测到的 SKILL.md 语言
英语
星标
179
分支
21

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
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) {
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看