modern-csharp-coding-standards
Use when writing modern C# code, optimizing performance-critical paths, or designing APIs with C# 12+ features
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when writing modern C# code, optimizing performance-critical paths, or designing APIs with C# 12+ features
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | modern-csharp-coding-standards |
| description | Use when writing modern C# code, optimizing performance-critical paths, or designing APIs with C# 12+ features |
| invocable | false |
Use this skill when:
record types and init-only propertiesswitch expressions and patterns extensivelySpan<T> and Memory<T> for performance-critical codereadonly record struct for value objectsUse record types for DTOs, messages, events, and domain entities.
// Simple immutable DTO
public record CustomerDto(string Id, string Name, string Email);
// Record with validation in constructor
public record EmailAddress
{
public string Value { get; init; }
public EmailAddress(string value)
{
if (string.IsNullOrWhiteSpace(value) || !value.Contains('@'))
throw new ArgumentException("Invalid email address", nameof(value));
Value = value;
}
}
// Records with collections - use IReadOnlyList
public record ShoppingCart(
string CartId,
string CustomerId,
IReadOnlyList<CartItem> Items
)
{
public decimal Total => Items.Sum(item => item.Price * item.Quantity);
}
When to use record class vs record struct:
record class (default): Reference types, use for entities, aggregates, DTOs with multiple propertiesrecord struct: Value types, use for value objects (see next section)Value objects should always be readonly record struct for performance and value semantics. Use explicit conversions, never implicit operators.
public readonly record struct OrderId(string Value)
{
public OrderId(string value) : this(
!string.IsNullOrWhiteSpace(value)
? value
: throw new ArgumentException("OrderId cannot be empty", nameof(value)))
{ }
public override string ToString() => Value;
}
public readonly record struct Money(decimal Amount, string Currency);
public readonly record struct CustomerId(Guid Value)
{
public static CustomerId New() => new(Guid.NewGuid());
}
See value-objects-and-patterns.md for complete examples including multi-value objects, factory patterns, and the no-implicit-conversion rule.
Use switch expressions, property patterns, relational patterns, and list patterns for cleaner code.
public decimal CalculateDiscount(Order order) => order switch
{
{ Total: > 1000m } => order.Total * 0.15m,
{ Total: > 500m } => order.Total * 0.10m,
{ Total: > 100m } => order.Total * 0.05m,
_ => 0m
};
See value-objects-and-patterns.md for full pattern matching examples.
Enable nullable reference types in your project and handle nulls explicitly.
// In .csproj
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
// Explicit nullability
public string? FindUserName(string userId)
{
var user = _repository.Find(userId);
return user?.Name;
}
// Pattern matching with null checks
public decimal GetDiscount(Customer? customer) => customer switch
{
null => 0m,
{ IsVip: true } => 0.20m,
{ OrderCount: > 10 } => 0.10m,
_ => 0.05m
};
// Guard clauses with ArgumentNullException.ThrowIfNull (C# 11+)
public void ProcessOrder(Order? order)
{
ArgumentNullException.ThrowIfNull(order);
// order is now non-nullable in this scope
Console.WriteLine(order.Id);
}
Avoid abstract base classes. Use interfaces + composition. Use static helpers for shared logic. Use records with factory methods for variants.
See composition-and-error-handling.md for full examples.
// Async all the way - always accept CancellationToken
public async Task<Order> GetOrderAsync(string orderId, CancellationToken cancellationToken)
{
var order = await _repository.GetAsync(orderId, cancellationToken);
return order;
}
// ValueTask for frequently-called, often-synchronous methods
public ValueTask<Order?> GetCachedOrderAsync(string orderId, CancellationToken cancellationToken)
{
if (_cache.TryGetValue(orderId, out var order))
return ValueTask.FromResult<Order?>(order);
return GetFromDatabaseAsync(orderId, cancellationToken);
}
// IAsyncEnumerable for streaming
public async IAsyncEnumerable<Order> StreamOrdersAsync(
string customerId,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var order in _repository.StreamAllAsync(cancellationToken))
{
if (order.CustomerId == customerId)
yield return order;
}
}
Key rules:
CancellationToken with = defaultConfigureAwait(false) in library code.Result or .Wait())Use Span<T> for synchronous zero-allocation operations, Memory<T> for async, and ArrayPool<T> for large temporary buffers.
See performance-and-api-design.md for complete Span/Memory examples and the API design section.
For expected errors, use Result<T, TError> instead of exceptions. Use exceptions only for unexpected/system errors.
See composition-and-error-handling.md for the full Result type implementation and usage examples.
Banned: AutoMapper, Mapster, ExpressMapper. Use explicit mapping extension methods instead. Use UnsafeAccessorAttribute (.NET 8+) when you genuinely need private member access.
See anti-patterns-and-reflection.md for full guidance.
// File: Domain/Orders/Order.cs
namespace MyApp.Domain.Orders;
// 1. Primary domain type
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 });
}
}
// 2. Enums for state
public enum OrderStatus { Draft, Submitted, Processing, Completed, Cancelled }
// 3. Related types
public record OrderItem(ProductId ProductId, Quantity Quantity, Money UnitPrice)
{
public Money Total => new(UnitPrice.Amount * Quantity.Value, UnitPrice.Currency);
}
// 4. Value objects
public readonly record struct OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
}
// 5. Errors
public readonly record struct OrderError(string Code, string Message);
record for DTOs, messages, and domain entitiesreadonly record struct for value objectsswitch expressionsCancellationToken in all async methodsSpan<T> and Memory<T> for high-performance scenariosIEnumerable<T>, IReadOnlyList<T>)Result<T, TError> for expected errorsArrayPool<T> for large allocationsreadonly record struct).Result, .Wait())byte[] when Span<byte> sufficesCancellationToken parametersArrayPool)See anti-patterns-and-reflection.md for detailed anti-pattern examples.
Use when creating, updating, or standardizing GitHub Actions workflows for a .NET repository, especially C# libraries, NuGet packages, ABP/EAF web apps, or Angular frontends.
Bootstrap a complete production-ready agent harness in a repository — CLAUDE.md, .claude/, .devin/, skills/, rules/, ignore files, and sub-agents. Use when initializing AI agent support in a new repo, restructuring existing agent files into modern File-based Context conventions, or generating harness artifacts following Agent = Model + Harness principles. Targets Claude Code and Devin CLI only — does NOT create artifacts for OpenCode, Cursor, Gemini, Copilot or JetBrains AI. Do NOT use for building MCP servers (use building-mcp-servers).
Install and configure RTK (Rust Token Killer) — a high-performance CLI proxy that reduces LLM token consumption by 60-90%. Automatically rewrites shell commands to their rtk equivalents via hooks. Use when setting up a new dev environment or when token optimization is needed. Do NOT use when raw unfiltered output is required (use rtk proxy for that).
Apply Test-Driven Development to skill authoring — write pressure scenarios with subagents (test cases), observe baseline failures (RED), write the skill (GREEN), close loopholes through refactor cycles. Use when creating new skills, editing existing skills, or verifying skills work before deployment. Do NOT use for one-off solutions, project-specific conventions (put in AGENTS.md/CLAUDE.md), or mechanical constraints enforceable via regex/validation. Companion skills: brainstorming (for design phase), create-agent-harness (for bootstrapping agent harness in a repo).
Use when working with ABP module system, dependency injection, base classes, time handling, business exceptions, or localization patterns
Use when designing and implementing RESTful APIs with ASP.NET Core, or when applying security, error handling, documentation, testing, or performance optimization to API endpoints