/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.
$ npx -y skills add softspark/ai-toolkit --skill csharp-patterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/csharp-patterns
Context preview
The summary Claude sees to decide when to auto-load this skill.
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.
SKILL.md
csharp-patterns.SKILL.mdname: 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
<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)
<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
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
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
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
var activeUsers = users
.Where(u => u.IsActive)
.OrderByDescending(u => u.LastLogin)
.Select(u => new UserDto(u.Id, u.Name))
.ToList();Async/Await
// 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)
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
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
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
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 + FluentAsser
Read more
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
<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)
<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
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
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
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
var activeUsers = users
.Where(u => u.IsActive)
.OrderByDescending(u => u.LastLogin)
.Select(u => new UserDto(u.Id, u.Name))
.ToList();Async/Await
// 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)
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
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
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
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 + FluentAsser
Professional-grade AI coding toolkit with multi-platform support. Machine-enforced safety, 109 skills, 44 agents, expanded lifecycle hooks, persona presets, experimental opt-in plugin packs, and benchmark tooling — works with Claude Code, Claude Chat/Cowork,
Repo: softspark/ai-toolkit
Other skills on ai-toolkit.
- /ai-toolkit-rules
Mandatory engineering, security, testing, git, performance, quality, and response rules. Claude MUST load this skill for every technical, coding, debugging, review, architecture, DevOps, data, or file-editing task in Chat or Cowork.
Open skill - /mem-search
Search past coding sessions using natural language. Finds relevant observations, decisions, and context from previous work.
Open skill - /a11y-validate
Accessibility validator: WCAG 2.1 AA, EN 301 549, EAA. Triggers: a11y, accessibility, WCAG, EAA, ARIA, contrast, keyboard, screen reader.
Open skill - /agent-creator
Creates new specialized agents with frontmatter, tools, delegation. Triggers: new agent, create agent, agent scaffold, specialized agent.
Open skill - /analyze
Analyzes code quality, complexity, patterns across codebase. Triggers: quality report, hotspot scan, code analysis, architecture signal.
Open skill - /api-patterns
REST/GraphQL API design: naming, versioning, pagination, idempotency, OpenAPI. Triggers: API design, REST, GraphQL, OpenAPI, Swagger, idempotency, rate limit.
Open skill

