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
آخر نشاط في المصدر
١٢ مايو ٢٠٢٦ في ١٣:٠٢
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٧٩
التفرعات
٢١

خيارات التثبيت

يُحدَّد 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