Community코딩 & 개발github.com

modern-csharp-coding-standards

레코드, 패턴 매칭, 값 객체, async/await, Span<T>/Memory<T> 및 모범 API 설계 패턴을 사용하여 모던하고 고성능인 C# 코드를 작성합니다. C# 12+ 기능을 활용한 함수형 스타일 프로그래밍을 강조합니다.

modern-csharp-coding-standards란 무엇인가요?

이 스킬은 최신 C# 언어 기능과 성능 모범 사례를 활용하여 고품질, 고성능 C# 코드를 작성하는 방법을 정의합니다. 핵심 원칙으로는 기본적인 불변성(레코드 및 init 전용 속성 사용), 타입 안전성(nullable 참조 타입 및 값 객체 활용), 광범위한 패턴 매칭(switch 식 및 패턴 사용), 비동기 프로그래밍(적절한 취소 지원 포함), 그리고 제로 할당 패턴(Span<T> 및 Memory<T> 사용)을 강조합니다. 또한, 추상 기본 클래스 대신 컴포지션을 선호하고, 값 객체에는 readonly record struct를 사용하며, 예상 가능한 오류 처리에 예외 대신 Result<T, TError> 타입을 사용하도록 권장합니다. AutoMapper와 같은 리플렉션 기반 매핑 라이브러리는 금지되며, 명시적인 확장 메서드 매핑이 권장됩니다. 이 스킬은 C# 12+ 기능을 활용하는 현대적인 C# 코드베이스, 라이브러리, 서비스를 작업하는 개발자를 위해 설계되었으며, 특히 성능에 민감한 코드 경로, 도메인 모델링, 비동기 애플리케이션 구축에 유용합니다. Claude Code, Cursor 및 기타 AI 에이전트와의 사용을 위해 최적화되었습니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/csharp-coding-standards

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Modern C# 코딩 표준

이 스킬을 사용해야 할 때

다음과 같은 상황에서 이 스킬을 사용하세요:

  • 새로운 C# 코드를 작성하거나 기존 코드를 리팩터링할 때
  • 라이브러리나 서비스의 공개 API를 설계할 때
  • 성능이 중요한 코드 경로를 최적화할 때
  • 강력한 타입으로 도메인 모델을 구현할 때
  • async/await을 많이 사용하는 애플리케이션을 구축할 때
  • 바이너리 데이터, 버퍼 또는 높은 처리량 시나리오를 다룰 때

참고 파일

핵심 원칙

  1. 기본값은 불변성 - record 타입과 init 전용 속성 사용
  2. 타입 안전성 - Nullable 참조 타입과 값 객체 활용
  3. 최신 패턴 매칭 - switch 식과 패턴을 광범위하게 사용
  4. 모든 곳에서 비동기 - 적절한 취소 지원과 함께 async API 선호
  5. 제로 할당 패턴 - 성능이 중요한 코드에는 Span<T>와 Memory<T> 사용
  6. API 설계 - 추상화는 입력으로 받고, 적절히 구체적인 타입을 반환
  7. 상속보다는 합성 - 추상 기본 클래스는 피하고 합성 선호
  8. 값 객체는 구조체로 - 값 객체에는 readonly record struct 사용

언어 패턴

불변 데이터를 위한 레코드 (C# 9+)

DTO, 메시지, 이벤트 및 도메인 엔터티에는 record 타입을 사용하세요.

// 간단한 불변 DTO
public record CustomerDto(string Id, string Name, string Email);

// 생성자에 유효성 검사가 있는 레코드
public record EmailAddress
{
    public string Value { get; init; }

    public EmailAddress(string value)
    {
        if (string.IsNullOrWhiteSpace(value) || !value.Contains('@'))
            throw new ArgumentException("유효하지 않은 이메일 주소", nameof(value));

        Value = value;
    }
}

// 컬렉션이 있는 레코드 - IReadOnlyList 사용
public record ShoppingCart(
    string CartId,
    string CustomerId,
    IReadOnlyList<CartItem> Items
)
{
    public decimal Total => Items.Sum(item => item.Price * item.Quantity);
}

record class vs record struct 사용 시기:

  • record class (기본값): 참조 타입, 여러 속성을 가진 엔터티, 집계, DTO에 사용
  • record struct: 값 타입, 값 객체에 사용 (다음 섹션 참조)

값 객체로서의 readonly record struct

값 객체는 성능과 값 의미론을 위해 항상 readonly record struct 여야 합니다. 명시적 변환을 사용하고 암시적 연산자는 절대 사용하지 마세요.

public readonly record struct OrderId(string Value)
{
    public OrderId(string value) : this(
        !string.IsNullOrWhiteSpace(value)
            ? value
            : throw new ArgumentException("OrderId는 비어 있을 수 없습니다", 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());
}

다중 값 객체, 팩토리 패턴 및 암시적 변환 금지 규칙을 포함한 전체 예제는 value-objects-and-patterns.md를 참조하세요.

패턴 매칭 (C# 8-12)

더 깔끔한 코드를 위해 switch 식, 속성 패턴, 관계형 패턴 및 리스트 패턴을 사용하세요.

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
};

전체 패턴 매칭 예제는 value-objects-and-patterns.md를 참조하세요.


Nullable 참조 타입 (C# 8+)

프로젝트에서 nullable 참조 타입을 활성화하고 null을 명시적으로 처리하세요.

// .csproj 파일
<PropertyGroup>
    <Nullable>enable</Nullable>
</PropertyGroup>

// 명시적 null 허용
public string? FindUserName(string userId)
{
    var user = _repository.Find(userId);
    return user?.Name;
}

// null 검사가 포함된 패턴 매칭
public decimal GetDiscount(Customer? customer) => customer switch
{
    null => 0m,
    { IsVip: true } => 0.20m,
    { OrderCount: > 10 } => 0.10m,
    _ => 0.05m
};

// ArgumentNullException.ThrowIfNull을 사용한 가드 절 (C# 11+)
public void ProcessOrder(Order? order)
{
    ArgumentNullException.ThrowIfNull(order);
    // 이제 이 범위에서 order는 null이 아닙니다.
    Console.WriteLine(order.Id);
}

상속보다는 합성

추상 기본 클래스는 피하세요. 인터페이스 + 합성을 사용하세요. 공유 로직에는 정적 헬퍼를 사용하세요. 변형에는 팩토리 메서드가 있는 레코드를 사용하세요.

전체 예제는 composition-and-error-handling.md를 참조하세요.


성능 패턴

Async/Await 모범 사례

// 완전 비동기 - 항상 CancellationToken을 받아들임
public async Task<Order> GetOrderAsync(string orderId, CancellationToken cancellationToken)
{
    var order = await _repository.GetAsync(orderId, cancellationToken);
    return order;
}

// 자주 호출되며 대부분 동기적인 메서드에는 ValueTask 사용
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
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;
    }
}

핵심 규칙:

  • 항상 = default와 함께 CancellationToken을 받아들임
  • 라이브러리 코드에서는 ConfigureAwait(false) 사용
  • 비동기 코드를 절대 차단하지 마세요 (.Result 또는 .Wait() 사용 금지)
  • 타임아웃에는 연결된 CancellationTokenSource 사용

Span 및 Memory

동기식 제로 할당 작업에는 Span<T>를, 비동기 작업에는 Memory<T>를, 대규모 임시 버퍼에는 ArrayPool<T>를 사용하세요.

전체 Span/Memory 예제 및 API 설계 섹션은 performance-and-api-design.md를 참조하세요.


오류 처리: Result 타입

예상되는 오류에는 예외 대신 Result<T, TError>를 사용하세요. 예외는 예상치 못한/시스템 오류에만 사용하세요.

전체 Result 타입 구현 및 사용 예제는 composition-and-error-handling.md를 참조하세요.


리플렉션 기반 메타프로그래밍 피하기

금지: AutoMapper, Mapster, ExpressMapper. 대신 명시적 매핑 확장 메서드를 사용하세요. 비공개 멤버에 진정으로 접근해야 하는 경우 UnsafeAccessorAttribute (.NET 8+)를 사용하세요.

전체 지침은 anti-patterns-and-reflection.md를 참조하세요.


코드 구성

// 파일: Domain/Orders/Order.cs

namespace MyApp.Domain.Orders;

// 1. 기본 도메인 타입
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", "초안 상태의 주문에만 항목을 추가할 수 있습니다"));

        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. 상태를 위한 열거형
public enum OrderStatus { Draft, Submitted, Processing, Completed, Cancelled }

// 3. 관련 타입
public record OrderItem(ProductId ProductId, Quantity Quantity, Money UnitPrice)
{
    public Money Total => new(UnitPrice.Amount * Quantity.Value, UnitPrice.Currency);
}

// 4. 값 객체
public readonly record struct OrderId(Guid Value)
{
    public static OrderId New() => new(Guid.NewGuid());
}

// 5. 오류
public readonly record struct OrderError(string Code, string Message);

모범 사례 요약

해야 할 것

  • DTO, 메시지 및 도메인 엔터티에는 record 사용
  • 값 객체에는 readonly record struct 사용
  • switch 식과 함께 패턴 매칭 활용
  • nullable 참조 타입 활성화 및 준수
  • 모든 I/O 작업에 async/await 사용
  • 모든 비동기 메서드에서 CancellationToken 받아들이기
  • 고성능 시나리오에서는 Span<T> 및 Memory<T> 사용
  • 추상화 (IEnumerable<T>, IReadOnlyList<T>)를 입력으로 받아들이기
  • 예상되는 오류에는 Result<T, TError> 사용
  • 대규모 할당에는 ArrayPool<T>로 버퍼 풀링
  • 상속보다는 합성 선호

하지 말아야 할 것

  • 레코드가 가능한 경우 가변 클래스 사용 금지
  • 값 객체에 클래스 사용 금지 (readonly record struct 사용)
  • 깊은 상속 계층 구조 생성 금지
  • nullable 참조 타입 경고 무시 금지
  • 비동기 코드 차단 금지 (.Result, .Wait())
  • Span<byte>로 충분할 때 byte[] 사용 금지
  • CancellationToken 매개변수 잊지 않기
  • API에서 가변 컬렉션 반환 금지
  • 예상되는 비즈니스 오류에 예외를 던지지 않기
  • 대규모 배열을 반복적으로 할당하지 않기 (ArrayPool 사용)

자세한 안티 패턴 예제는 anti-patterns-and-reflection.md를 참조하세요.


추가 리소스

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

관련 스킬