Loading...
Loading...
Write modern, high-performance C# code using records, pattern matching, value objects, async/await, Span<T>/Memory<T>, and best-practice API design patterns. Emphasizes functional-style programming with C# 12+ features. Use when writing new C# code or refactoring existing code, designing public APIs for libraries or services, optimizing performance-critical code paths, or building async/await-heavy applications.
npx skill4agent add wshaddix/dotnet-skills modern-csharp-coding-standardsrecordinitswitchSpan<T>Memory<T>readonly record struct| Element | Convention | Example |
|---|---|---|
| Namespaces | PascalCase, dot-separated | |
| Classes, Records, Structs | PascalCase | |
| Interfaces | | |
| Methods | PascalCase | |
| Properties | PascalCase | |
| Events | PascalCase | |
| Public constants | PascalCase | |
| Private fields | | |
| Parameters, locals | camelCase | |
| Type parameters | | |
| Enum members | PascalCase | |
Asyncpublic Task<Order> GetOrderAsync(int id);
public ValueTask SaveChangesAsync(CancellationToken ct);
Exception: Event handlers and interface implementations where the framework does not use the `Async` suffix (e.g., ASP.NET Core middleware `InvokeAsync` is already named by the framework).ishascanshouldpublic bool IsActive { get; set; }
public bool HasOrders { get; }
public bool CanDelete(Order order);public IReadOnlyList<Order> Orders { get; }
public Dictionary<string, int> CountsByName { get; }OrderService.cs -> public class OrderService
IOrderRepository.cs -> public interface IOrderRepository
OrderStatus.cs -> public enum OrderStatus
OrderSummary.cs -> public record OrderSummarynamespace MyApp.Services;
public class OrderService { }using<ImplicitUsings>enable</ImplicitUsings>usingSystem.*if (order.IsValid)
{
Process(order);
}public string FullName => $"{FirstName} {LastName}";
public override string ToString() => $"Order #{Id}";varvarvar orders = new List<Order>();
var customer = GetCustomerById(id);
IOrderRepository repo = serviceProvider.GetRequiredService<IOrderRepository>();
decimal total = CalculateTotal(items);if (order is not null) { }
if (order is { Status: OrderStatus.Active }) { }
var name = customer?.Name ?? "Unknown";
var orders = customer?.Orders ?? [];
items ??= [];string.Formatvar message = $"Order {orderId} totals {total:C2}";
var json = $$"""
{
"id": {{orderId}},
"name": "{{name}}"
}
""";public class OrderService
{
private readonly IOrderRepository _repo;
internal void ProcessBatch() { }
}access (public/private/protected/internal) -> static -> extern -> new ->
virtual/abstract/override/sealed -> readonly -> volatile -> async -> partialpublic static readonly int MaxSize = 100;
protected virtual async Task<Order> LoadAsync() => await repo.GetDefaultAsync();
public sealed override string ToString() => Name;public sealed class OrderService(IOrderRepository repo)
{
}public sealed class OrderProcessor(IValidator validator, INotifier notifier)
{
public async Task ProcessAsync(Order order)
{
await validator.ValidateAsync(order);
await notifier.NotifyAsync(order);
}
}public interface IOrderReader
{
Task<Order?> GetByIdAsync(int id, CancellationToken ct = default);
Task<IReadOnlyList<Order>> GetAllAsync(CancellationToken ct = default);
}
public interface IOrderWriter
{
Task<Order> CreateAsync(Order order, CancellationToken ct = default);
Task UpdateAsync(Order order, CancellationToken ct = default);
}public record OrderBuilder
{
public OrderId Id { get; init; } = OrderId.New();
public CustomerId CustomerId { get; init; } = CustomerId.New();
public Money Total { get; init; } = new Money(100m, "USD");
public IReadOnlyList<OrderItem> Items { get; init; } = Array.Empty<OrderItem>();
public Order Build() => new(Id, CustomerId, Total, Items);
}
[Fact]
public void CalculateDiscount_LargeOrder_AppliesCorrectDiscount()
{
var baseOrder = new OrderBuilder().Build();
var largeOrder = baseOrder with { Total = new Money(1500m, "USD") };
var discount = _service.CalculateDiscount(largeOrder);
discount.Should().Be(new Money(225m, "USD"));
}
[Theory]
[InlineData("ORD-12345", true)]
[InlineData("INVALID", false)]
public void TryParseOrderId_VariousInputs_ReturnsExpectedResult(
string input, bool expected)
{
var result = OrderIdParser.TryParse(input.AsSpan(), out var orderId);
result.Should().Be(expected);
}
[Fact]
public void Money_Add_SameCurrency_ReturnsSum()
{
var money1 = new Money(100m, "USD");
var money2 = new Money(50m, "USD");
var result = money1.Add(money2);
result.Should().Be(new Money(150m, "USD"));
}
[Fact]
public void Money_Add_DifferentCurrency_ThrowsException()
{
var usd = new Money(100m, "USD");
var eur = new Money(50m, "EUR");
var act = () => usd.Add(eur);
act.Should().Throw<InvalidOperationException>()
.WithMessage("*different currencies*");
}CancellationTokendefaultpublic async Task<Order> GetOrderAsync(int id, CancellationToken ct = default)
{
return await _repo.GetByIdAsync(id, ct);
}CancellationToken/// <summary>
/// Retrieves an order by its unique identifier.
/// </summary>
/// <param name="id">The order identifier.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The order, or <see langword="null"/> if not found.</returns>
public Task<Order?> GetByIdAsync(int id, CancellationToken ct = default);InternalsVisibleTopublic string Name { get; }namespace MyApp.Domain.Orders;
public record Order(
OrderId Id,
CustomerId CustomerId,
Money Total,
OrderStatus Status,
IReadOnlyList<OrderItem> Items
)
{
public bool IsCompleted => Status is OrderStatus.Completed;
public Result<Order, OrderError> AddItem(OrderItem item)
{
if (Status is not OrderStatus.Draft)
return Result<Order, OrderError>.Failure(
new OrderError("ORDER_NOT_DRAFT", "Can only add items to draft orders"));
var newItems = Items.Append(item).ToList();
var newTotal = new Money(
Items.Sum(i => i.Total.Amount) + item.Total.Amount,
Total.Currency);
return Result<Order, OrderError>.Success(
this with { Items = newItems, Total = newTotal });
}
}
public enum OrderStatus
{
Draft,
Submitted,
Processing,
Completed,
Cancelled
}
public record OrderItem(
ProductId ProductId,
Quantity Quantity,
Money UnitPrice
)
{
public Money Total => new(
UnitPrice.Amount * Quantity.Value,
UnitPrice.Currency);
}
public readonly record struct OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
}
public readonly record struct OrderError(string Code, string Message);Directory.Build.props.editorconfig<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-all</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>.editorconfig[*.cs]
csharp_style_namespace_declarations = file_scoped:warning
csharp_prefer_braces = true:warning
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
dotnet_style_require_accessibility_modifiers = always:warning
csharp_style_prefer_pattern_matching = true:suggestionrecordreadonly record structswitchCancellationTokenSpan<T>Memory<T>IEnumerable<T>IReadOnlyList<T>Result<T, TError>ConfigureAwait(false)ArrayPool<T>readonly record struct.Result.Wait()byte[]Span<byte>CancellationTokenstringArrayPoolrequiredvar