internal/skills/content/csharp-guide/SKILL.md
C# language guardrails, patterns, and best practices for AI-assisted development. Use when working with C# files (.cs, .csx), .csproj, .sln, or when the user mentions C#/.NET. Provides nullable reference types, async/await patterns, LINQ guidelines, and testing standards specific to this project's coding standards.
npx skillsauth add ar4mirez/samuel csharp-guideInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Applies to: C# 12+, .NET 8+, ASP.NET Core, Console Apps, Libraries
readonly, and init properties for data typesasync/await end-to-end; never block on async codeIServiceCollection; no service locatorArgumentException<Nullable>enable</Nullable> and <ImplicitUsings>enable</ImplicitUsings> in .csproj.csproj (avoid floating * versions)dotnet restore before committing after dependency changesdotnet list package --vulnerable before addingPascalCase | Private fields: _camelCase with underscore prefixIServiceName | Async methods: suffix with Asyncvar when the type is obvious from the right side; explicit types otherwisenamespace MyApp.Services;).editorconfig for consistent formatting across the teamdotnet format before every commit<Nullable>enable</Nullable> in every .csproj#pragma warning disable unless documented[NotNullWhen], [MaybeNullWhen], [NotNull] attributes for complex nullability! sparingly and only with a justifying commentis not null over != null for null checks?? and ??= for default values; ?. for conditional access// Good: explicit nullability contract
public User? FindByEmail(string email)
{
ArgumentException.ThrowIfNullOrWhiteSpace(email);
return _users.FirstOrDefault(u => u.Email == email);
}
// Good: guard clause with null-coalescing
public void Process(Order order)
{
var customer = order.Customer
?? throw new InvalidOperationException("Order must have a customer.");
// ...
}
async/await end-to-end; never call .Result or .Wait() (deadlock risk)Async: GetUserAsync, SaveOrderAsyncValueTask<T> for hot paths that frequently complete synchronouslyConfigureAwait(false) in library code (not in ASP.NET Core controllers)CancellationToken in all async method signatures that perform I/OIAsyncEnumerable<T> for streaming large result setsCancellationTokenSourcepublic async Task<User> GetUserAsync(
int id, CancellationToken cancellationToken = default)
{
return await _dbContext.Users
.AsNoTracking()
.FirstOrDefaultAsync(u => u.Id == id, cancellationToken)
?? throw new NotFoundException(nameof(User), id);
}
AsNoTracking() for read-only EF Core queriesToListAsync() / ToArrayAsync() before returningCount() when Any() sufficesSelect to project only needed fields (avoid loading full entities)// Good: projection, no-tracking, materialized
var activeEmails = await _dbContext.Users
.AsNoTracking()
.Where(u => u.IsActive)
.Select(u => u.Email)
.ToListAsync(cancellationToken);
// Bad: side effect in LINQ
var results = items.Select(i => { i.Processed = true; return i; }); // Don't do this
MySolution/
├── MySolution.sln
├── src/
│ ├── MySolution.Api/ # ASP.NET Core host / entry point
│ │ ├── Controllers/
│ │ ├── Middleware/
│ │ ├── Program.cs
│ │ └── MySolution.Api.csproj
│ ├── MySolution.Application/ # Use cases, commands, queries (CQRS)
│ │ ├── Commands/
│ │ ├── Queries/
│ │ ├── Interfaces/
│ │ └── MySolution.Application.csproj
│ ├── MySolution.Domain/ # Entities, value objects, domain events
│ │ ├── Entities/
│ │ ├── ValueObjects/
│ │ ├── Exceptions/
│ │ └── MySolution.Domain.csproj
│ └── MySolution.Infrastructure/ # EF Core, external services, file I/O
│ ├── Persistence/
│ ├── Services/
│ └── MySolution.Infrastructure.csproj
├── tests/
│ ├── MySolution.UnitTests/
│ │ └── MySolution.UnitTests.csproj
│ ├── MySolution.IntegrationTests/
│ │ └── MySolution.IntegrationTests.csproj
│ └── MySolution.ArchTests/ # Architecture rule tests (optional)
│ └── MySolution.ArchTests.csproj
├── .editorconfig
├── Directory.Build.props # Shared build properties
└── README.md
src/ structurepublic sealed class OrderService
{
private readonly IOrderRepository _repository;
public OrderService(IOrderRepository repository)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public async Task<OrderSummary> GetSummaryAsync(
int orderId, CancellationToken ct = default)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(orderId);
var order = await _repository.GetByIdAsync(orderId, ct)
?? throw new NotFoundException(nameof(Order), orderId);
return new OrderSummary(order.Id, order.Total, order.Status);
}
}
// Switch expression with property patterns
public decimal CalculateDiscount(Customer customer) => customer switch
{
{ MembershipLevel: "Gold", YearsActive: > 5 } => 0.20m,
{ MembershipLevel: "Gold" } => 0.15m,
{ MembershipLevel: "Silver" } => 0.10m,
{ TotalOrders: > 100 } => 0.05m,
_ => 0m,
};
// Relational and logical patterns
public string ClassifyTemperature(double temp) => temp switch
{
< 0 => "Freezing",
>= 0 and < 15 => "Cold",
>= 15 and < 25 => "Moderate",
>= 25 and < 35 => "Warm",
>= 35 => "Hot",
};
// Type pattern in is-expression
public static string Describe(object value) => value switch
{
int n when n < 0 => $"Negative integer: {n}",
int n => $"Positive integer: {n}",
string { Length: 0 } => "Empty string",
string s => $"String of length {s.Length}",
null => "null",
_ => $"Unknown: {value.GetType().Name}",
};
// Record for DTOs and value objects (value equality, immutable)
public sealed record OrderSummary(int Id, decimal Total, OrderStatus Status);
// Record with validation
public sealed record EmailAddress
{
public string Value { get; }
public EmailAddress(string value)
{
ArgumentException.ThrowIfNullOrWhiteSpace(value);
if (!value.Contains('@'))
throw new ArgumentException("Invalid email format.", nameof(value));
Value = value;
}
}
// Record struct for high-performance value types (no heap allocation)
public readonly record struct Coordinate(double Latitude, double Longitude);
// Nondestructive mutation with `with`
var updated = original with { Status = OrderStatus.Shipped };
// Producing an async stream
public async IAsyncEnumerable<LogEntry> StreamLogsAsync(
DateTime since,
[EnumeratorCancellation] CancellationToken ct = default)
{
await foreach (var batch in _logSource.ReadBatchesAsync(since, ct))
{
foreach (var entry in batch.Entries)
{
ct.ThrowIfCancellationRequested();
yield return entry;
}
}
}
// Consuming an async stream
await foreach (var log in StreamLogsAsync(DateTime.UtcNow.AddHours(-1), ct))
{
Console.WriteLine($"[{log.Timestamp}] {log.Message}");
}
// Registration in Program.cs (or a ServiceCollectionExtensions class)
public static IServiceCollection AddApplicationServices(
this IServiceCollection services)
{
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IOrderService, OrderService>();
services.AddSingleton<IClock, SystemClock>();
services.AddHttpClient<IPaymentGateway, StripePaymentGateway>(client =>
{
client.BaseAddress = new Uri("https://api.stripe.com/");
client.Timeout = TimeSpan.FromSeconds(10);
});
return services;
}
// Constructor injection (no service locator, no static access)
public sealed class OrderService : IOrderService
{
private readonly IOrderRepository _repository;
private readonly IClock _clock;
public OrderService(IOrderRepository repository, IClock clock)
{
_repository = repository;
_clock = clock;
}
}
[Fact] and [Theory]MethodName_Scenario_ExpectedResultShould calls for same concept OK)[Theory] with [InlineData] for parameterized testspublic sealed class OrderServiceTests
{
private readonly IOrderRepository _repository = Substitute.For<IOrderRepository>();
private readonly IClock _clock = Substitute.For<IClock>();
private readonly OrderService _sut;
public OrderServiceTests()
{
_sut = new OrderService(_repository, _clock);
}
[Fact]
public async Task GetSummaryAsync_ExistingOrder_ReturnsSummary()
{
// Arrange
var order = new Order { Id = 1, Total = 99.99m, Status = OrderStatus.Pending };
_repository.GetByIdAsync(1, Arg.Any<CancellationToken>()).Returns(order);
// Act
var result = await _sut.GetSummaryAsync(1);
// Assert
result.Should().NotBeNull();
result.Id.Should().Be(1);
result.Total.Should().Be(99.99m);
result.Status.Should().Be(OrderStatus.Pending);
}
[Fact]
public async Task GetSummaryAsync_MissingOrder_ThrowsNotFoundException()
{
// Arrange
_repository.GetByIdAsync(99, Arg.Any<CancellationToken>())
.Returns((Order?)null);
// Act
var act = () => _sut.GetSummaryAsync(99);
// Assert
await act.Should().ThrowAsync<NotFoundException>()
.WithMessage("*Order*99*");
}
[Theory]
[InlineData(0)]
[InlineData(-1)]
[InlineData(-100)]
public async Task GetSummaryAsync_InvalidId_ThrowsArgumentException(int invalidId)
{
var act = () => _sut.GetSummaryAsync(invalidId);
await act.Should().ThrowAsync<ArgumentOutOfRangeException>();
}
}
public sealed class OrdersApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public OrdersApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Replace real DB with in-memory for tests
services.RemoveAll<DbContextOptions<AppDbContext>>();
services.AddDbContext<AppDbContext>(opts =>
opts.UseInMemoryDatabase("TestDb"));
});
}).CreateClient();
}
[Fact]
public async Task GetOrder_ReturnsOk_WhenOrderExists()
{
var response = await _client.GetAsync("/api/orders/1");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<OrderSummary>();
body.Should().NotBeNull();
body!.Id.Should().Be(1);
}
}
dotnet new sln # Create solution
dotnet new webapi -n MyApp.Api # New Web API project
dotnet sln add src/MyApp.Api # Add project to solution
dotnet restore # Restore packages
dotnet build --no-restore # Build
dotnet test --no-build --verbosity normal # Run tests
dotnet test --collect:"XPlat Code Coverage" # With coverage
dotnet format # Format code
dotnet publish -c Release -o ./publish # Publish for deployment
<!-- Directory.Build.props (shared across all projects) -->
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-recommended</AnalysisLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers"
Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="SonarAnalyzer.CSharp"
Version="9.32.0" PrivateAssets="all" />
</ItemGroup>
</Project>
# .editorconfig (key settings)
[*.cs]
indent_style = space
indent_size = 4
dotnet_sort_system_directives_first = true
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion
csharp_style_prefer_pattern_matching = true:suggestion
csharp_prefer_simple_using_statement = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
For detailed patterns and examples, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.