| name | coding-conventions |
| description | .NET/C#의 코딩 규약, 명명 규칙, 레이아웃, C# 12/13/14의 최신 기능 활용 가이드라인을 정의합니다. C#/.NET 코드 작성 시, 클래스·메서드 명명 시, 코드 포맷팅 시, 또는 사용자가 코딩 규약, 명명 규칙, C# 모범 사례, Primary Constructors, Collection Expressions, field 키워드에 대해 언급했을 때 사용합니다. |
Coding Conventions
개요
이 SKILL은 개발되는 모든 .NET 프로젝트에 적용되는 코딩 규약을 정의합니다. .NET 8 이후의 최신 기능(C# 12/13/14, .NET 8/9/10)을 적극적으로 활용하여 가독성, 유지보수성, 성능이 높은 코드를 구현하는 것을 목적으로 합니다.
책임 범위
이 SKILL은 다음 범위를 다룹니다:
- .NET/C#의 최신 기능(C# 12/13/14, .NET 8/9/10) 활용 방침
- 명명 규칙 (Type, Member, Variable, Parameter)
- 코드 레이아웃 및 포맷
- 언어 기능 사용 방침 (Type inference, Collection, Exception handling)
- LINQ와 람다식의 모범 사례
- 모던 C# 구문의 권장 패턴
기본 방침
- .NET 8 이후의 최신 기능을 적극적으로 사용한다 (C# 12/13/14, .NET 8/9/10)
- 이전 버전과의 호환성이 필요한 경우를 제외하고 항상 최신 기능을 우선한다
- 오래된 언어 구문은 피한다
- 중요: 언더스코어 접두사(
_field) 사용은 절대 금지한다
- 중요: 중괄호 생략은 절대 금지한다 (1행으로 기술할 수 있는 경우에도 생략 불가)
- Microsoft 공식 코딩 규약을 따른다
- 일관성을 유지하고 팀 전체에서 동일한 스타일을 적용한다
.NET/C# 최신 기능 (버전별)
.NET 8 이후 각 버전에서 도입된 주요 기능을 적극적으로 활용합니다. 이전 버전과의 호환성이 필요한 경우를 제외하고 항상 최신 기능을 우선적으로 사용합니다.
C# 12 (.NET 8) - 2023년 11월 공식 릴리스
Primary Constructors
- 클래스나 struct 선언에서 파라미터를 정의하고 클래스 전체에서 사용할 수 있음
- 명시적인 필드 선언을 줄이고 초기화를 간소화함
좋은 예:
public class Person(string name, int age)
{
public string Name => name;
public int Age => age;
public void Display()
{
Console.WriteLine($"{name} is {age} years old");
}
}
나쁜 예:
public class Person
{
private string name;
private int age;
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
public string Name => name;
public int Age => age;
}
Collection Expressions
- 대괄호와 스프레드 연산자를 사용하여 컬렉션을 간결하게 생성함
- 여러 컬렉션을 결합할 때 유용함
좋은 예:
int[] array = [1, 2, 3, 4, 5];
List<string> list = ["one", "two", "three"];
int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] combined = [..row0, ..row1];
Default Lambda Parameters
좋은 예:
var incrementBy = (int source, int increment = 1) => source + increment;
Console.WriteLine(incrementBy(5));
Console.WriteLine(incrementBy(5, 3));
Alias Any Type
- using 디렉티브로 복잡한 타입에 별칭을 붙일 수 있음
좋은 예:
using Point = (int x, int y);
using ProductList = System.Collections.Generic.List<(string Name, decimal Price)>;
Point origin = (0, 0);
ProductList products = [("Product1", 100m), ("Product2", 200m)];
C# 13 (.NET 9) - 2024년 11월 공식 릴리스
Params Collections
params 수식어를 배열 외의 컬렉션 타입에서도 사용 가능해짐
List<T>, Span<T>, ReadOnlySpan<T>, IEnumerable<T> 등에서 사용 가능
좋은 예:
public void ProcessItems(params List<string> items)
{
foreach (var item in items)
{
Console.WriteLine(item);
}
}
public void ProcessData(params ReadOnlySpan<int> data)
{
foreach (var value in data)
{
Process(value);
}
}
New Lock Type
System.Threading.Lock 타입을 사용하여 더 빠른 스레드 동기화를 구현함
- 기존
Monitor 기반 락보다 빠름
좋은 예:
private readonly Lock lockObject = new();
public void UpdateData()
{
lock (lockObject)
{
}
}
나쁜 예:
private readonly object lockObject = new();
public void UpdateData()
{
lock (lockObject)
{
}
}
Partial Properties and Indexers
- partial 프로퍼티와 인덱서를 사용할 수 있게 됨
- 정의와 구현을 분리할 수 있음
좋은 예:
public partial class DataModel
{
public partial string Name { get; set; }
}
public partial class DataModel
{
private string name;
public partial string Name
{
get => name;
set => name = value ?? throw new ArgumentNullException(nameof(value));
}
}
Implicit Index Access
- 객체 이니셜라이저에서
^ 연산자를 사용할 수 있게 됨
좋은 예:
var countdown = new TimerBuffer
{
buffer =
{
[^1] = 0,
[^2] = 1,
[^3] = 2
}
};
Ref Struct Enhancements
ref struct 타입이 인터페이스를 구현할 수 있게 됨
- 제네릭 타입에서
ref struct를 사용할 수 있게 됨 (allows ref struct 제약 조건)
좋은 예:
public ref struct SpanWrapper<T> : IEnumerable<T>
{
private Span<T> span;
public IEnumerator<T> GetEnumerator()
{
foreach (var item in span)
{
yield return item;
}
}
}
C# 14 (.NET 10) - 2025년 11월 릴리스 예정
Extension Members
- Extension Members를 활용하여 깔끔한 API 확장을 구현함
- 원래 타입을 오염시키지 않고 기능을 추가할 수 있음
좋은 예:
extension<TSource>(IEnumerable<TSource> source)
{
public bool IsEmpty => !source.Any();
public int Count => source.Count();
}
Field-Backed Properties
field 키워드를 사용하여 명시적인 backing field를 줄임
- 검증 로직을 간결하게 기술할 수 있음
- 언더스코어 접두사를 사용한 명시적인 backing field는 절대 금지
좋은 예:
public string Name
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
private string name;
public string Name
{
get => name;
set => name = value ?? throw new ArgumentNullException(nameof(value));
}
나쁜 예:
private string _name;
public string Name
{
get => _name;
set => _name = value ?? throw new ArgumentNullException(nameof(value));
}
Null-Conditional Assignment
?.를 사용하여 null 체크를 간결하게 기술함
- 중복되는 null 체크를 줄임
좋은 예:
customer?.Order = GetCurrentOrder();
나쁜 예:
if (customer != null)
{
customer.Order = GetCurrentOrder();
}
Implicit Span Conversions
- 성능 중시 코드에서는
Span<T>와 ReadOnlySpan<T>를 활용함
- 배열과 스팬 타입 간의 자동 변환을 이용함
명명 규칙 (Naming Conventions)
Pascal Casing
- 타입 이름 (class, record, struct, interface, enum)
- 퍼블릭 멤버 (프로퍼티, 메서드, 이벤트)
- 네임스페이스
좋은 예:
public class CustomerOrder
{
public string OrderId { get; set; }
public void ProcessOrder() { }
}
Camel Casing
- 로컬 변수
- 메서드 파라미터
- 프라이빗 필드 (언더스코어 접두사는 절대 사용하지 않음)
좋은 예:
public class OrderProcessor
{
private string customerName;
private int orderCount;
public void ProcessOrder(string orderId)
{
var customerName = GetCustomerName(orderId);
string processedResult = Process(customerName);
}
}
나쁜 예:
public class OrderProcessor
{
private string _customerName;
private int _orderCount;
}
Interface 명명
좋은 예:
public interface IOrderProcessor
{
void Process(Order order);
}
타입 파라미터 명명
좋은 예:
public class Repository<TEntity> where TEntity : class
{
public void Add(TEntity entity) { }
}
코드 레이아웃 (Code Layout)
들여쓰기
- 공백(Space) 4개를 사용함
- 탭(Tab)은 사용하지 않음
중괄호 (Curly Braces)
- Allman 스타일 (시작 중괄호와 종료 중괄호를 별도의 행에 배치)
- 중괄호 생략은 절대 금지 (1행으로 기술할 수 있는 경우에도 반드시 중괄호 사용)
좋은 예:
public void ProcessOrder(Order order)
{
if (order != null)
{
order.Process();
}
}
if (isValid)
{
Execute();
}
for (int i = 0; i < 10; i++)
{
Process(i);
}
나쁜 예:
if (isValid)
Execute();
for (int i = 0; i < 10; i++)
Process(i);
if (order != null) order.Process();
행 기술
- 1행에 하나의 statement만 기술함
- 1행에 하나의 선언만 기술함
- 메서드 정의와 프로퍼티 정의 사이에 빈 행을 하나 넣음
좋은 예:
public class Order
{
public string OrderId { get; set; }
public void Process()
{
var result = Validate();
Execute(result);
}
private bool Validate()
{
return OrderId != null;
}
}
네임스페이스
- 파일 스코프 네임스페이스(File-scoped namespace)를 사용함
좋은 예:
namespace YourProject.Orders;
public class OrderProcessor
{
}
나쁜 예:
namespace YourProject.Orders
{
public class OrderProcessor
{
}
}
using 디렉티브
- 네임스페이스 선언 바깥쪽에 배치함
- 알파벳 순으로 정렬함
좋은 예:
using System;
using System.Collections.Generic;
using System.Linq;
namespace YourProject.Orders;
타입과 변수
타입 지정
- 언어 키워드 (
string, int, bool)를 사용함
- 런타임 타입 (
System.String, System.Int32)은 사용하지 않음
좋은 예:
string name = "John";
int count = 10;
bool isValid = true;
나쁜 예:
String name = "John";
Int32 count = 10;
Boolean isValid = true;
타입 추론 (var)
- 타입이 할당되는 내용으로부터 명백한 경우에만
var를 사용함
- 기본 제공 타입(Built-in type)은 명시적으로 기술함
좋은 예:
var orders = new List<Order>();
var customer = GetCustomer();
int count = 10;
string name = "John";
나쁜 예:
var count = 10;
var name = "John";
문자열
문자열 보간 (String Interpolation)
좋은 예:
string message = $"Order {orderId} processed successfully";
나쁜 예:
string message = "Order " + orderId + " processed successfully";
StringBuilder
- 루프 내에서 대량의 텍스트를 추가하는 경우
StringBuilder를 사용함
좋은 예:
var builder = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
builder.Append($"Line {i}\n");
}
Raw String Literals
- 이스케이프 시퀀스보다 Raw String Literals를 우선함
좋은 예:
string json = """
{
"name": "John",
"age": 30
}
""";
컬렉션과 객체 초기화
컬렉션 초기화
- C# 12 이후의 Collection Expressions를 사용함 (앞부분의 "C# 12 최신 기능" 참조)
객체 이니셜라이저 (Object Initializer)
좋은 예:
var customer = new Customer
{
Name = "John",
Email = "john@example.com"
};
예외 처리 (Exception Handling)
구체적인 예외 캐치
- 일반적인
System.Exception 대신 구체적인 예외를 캐치함
좋은 예:
try
{
ProcessOrder(order);
}
catch (ArgumentNullException ex)
{
Logger.Error("Order is null", ex);
}
나쁜 예:
try
{
ProcessOrder(order);
}
catch (Exception ex)
{
Logger.Error("Error", ex);
}
using 문
- try-finally 대신
using 문을 사용함
좋은 예:
using var connection = new SqlConnection(connectionString);
connection.Open();
나쁜 예:
SqlConnection connection = null;
try
{
connection = new SqlConnection(connectionString);
connection.Open();
}
finally
{
connection?.Dispose();
}
LINQ
의미 있는 변수명
좋은 예:
var activeCustomers = from customer in customers
where customer.IsActive
select customer;
조기 필터링
where 절을 사용하여 조기에 데이터를 필터링함
좋은 예:
var result = customers
.Where(c => c.IsActive)
.Select(c => c.Name)
.ToList();
암시적 타입 지정
- LINQ 선언에서는 암시적 타입 지정을 사용함
좋은 예:
var query = from customer in customers
where customer.IsActive
select customer;
람다식 (Lambda Expressions)
이벤트 핸들러
좋은 예:
button.Click += (s, e) => ProcessClick();
파라미터 수식어
- C# 14 기능을 활용하여 타입 추론을 유지하면서 수식어를 사용함
좋은 예:
TryParse<int> parse = (text, out result) => int.TryParse(text, out result);
주석 (Comments)
단일 행 주석
- 간결한 설명에는
//를 사용함
- 주석 구분자 뒤에 공백을 하나 넣음
- 주석은 반드시 단독 행에 기술함 (코드와 같은 행에 기술 금지)
- 주석 앞에는 빈 행을 하나 넣음
좋은 예:
ProcessOrder(order);
var processor = new OrderProcessor();
var result = processor.ProcessOrder(order);
나쁜 예:
ProcessOrder(order);
var processor = new OrderProcessor();
var result = processor.ProcessOrder(order);
XML 문서
좋은 예:
public bool ProcessOrder(Order order)
{
}
정적 멤버 (Static Members)
클래스 이름에 의한 호출
좋은 예:
var result = OrderProcessor.ProcessOrder(order);
나쁜 예:
var processor = new OrderProcessor();
var result = processor.ProcessOrder(order);
체크리스트 (Checklist)
코드 작성 전
코드 작성 중
필수 규칙:
C# 12 이후 기능:
C# 13 이후 기능:
C# 14 이후 기능:
기본 규칙:
코드 작성 후