C# Code Style Guide
Overview
C# 코딩 표준 (POCU 기반, C# 9.0 기준)
Core Topics:
-
명명 규칙 (m접두어, b접두어, E/S접두어)
-
코드 작성 규칙
-
클래스 구조 순서
-
C# 9.0 패턴
Language Note
이 문서는 한국어를 기본으로 작성되었으며, 코드 예제와 기술 용어는 영어를 사용합니다. 섹션 제목은 영어로 통일하고, 설명과 주석은 한국어로 작성합니다.
-
섹션 제목: 영어 (Naming Conventions, Code Writing Rules 등)
-
설명/주석: 한국어
-
코드 예제: 영어 (변수명, 클래스명, 코드 코멘트)
-
표(Table) 헤더: 영어, 내용은 영어 또는 한국어 혼용 가능
Naming Conventions
Quick Reference
Element Convention Example
Class PascalCase PlayerManager , OrderService
Struct SPascalCase SUserID , SPlayerData
Interface IPascalCase IDisposable , IOrderService
Enum EPascalCase EDirection , EOrderStatus
Method (public) PascalCase (동사+명사) GetAge() , ProcessOrder()
Method (private) camelCase getAge() , processOrder()
Property PascalCase Name , OrderID
Private Field mPascalCase mAge , mOrderRepository
Local Variable camelCase totalAmount , isValid
Parameter camelCase customerId , orderDate
Constant ALL_CAPS MAX_RETRY_COUNT , DEFAULT_TIMEOUT
Static readonly ALL_CAPS MY_CONST_OBJECT
Boolean Variable bCamelCase / mbPascalCase bFired , mbIsEnabled
Boolean Property Is/Has/Can/Should IsFired , HasChildren
Private Member Pattern
public class OrderService { // Private fields: m + PascalCase private readonly IOrderRepository mOrderRepository; private readonly ILogger mLogger; private int mRetryCount; private bool mbIsProcessing; // boolean: mb prefix
// Constructor parameter: camelCase (no prefix)
public OrderService(IOrderRepository orderRepository, ILogger logger)
{
mOrderRepository = orderRepository;
mLogger = logger;
}
// Public method: PascalCase
public Order GetOrder(int orderId)
{
return getOrderInternal(orderId);
}
// Private method: camelCase
private Order getOrderInternal(int orderId)
{
return mOrderRepository.Find(orderId);
}
}
Enum and Struct Prefixes
// Enum: E prefix public enum EDirection { None, North, South, East, West }
// Bit flags enum: Flags suffix [Flags] public enum EVisibilityFlags { None = 0, Visible = 1, Hidden = 2, Collapsed = 4 }
// Struct: S prefix (readonly struct는 제외 가능) public struct SUserID { public int Value { get; } }
// readonly record struct: S prefix 불필요 public readonly record struct UserID(int Value);
Nullable Naming
// Nullable parameter: OrNull suffix public Animation GetAnimation(string nameOrNull) { if (nameOrNull == null) { return DefaultAnimation; } return mAnimations[nameOrNull]; }
// Nullable return: OrNull suffix public string GetNameOrNull() { return mbHasName ? mName : null; }
// Recursive function: Recursive suffix public int FibonacciRecursive(int n) { if (n <= 1) { return n; } return FibonacciRecursive(n - 1) + FibonacciRecursive(n - 2); }
Code Writing Rules
Prohibited Patterns
// [WRONG] var keyword var order = GetOrder(1); var items = new List<Item>();
// [CORRECT] Explicit type Order order = GetOrder(1); List<Item> items = new List<Item>();
// [WRONG] Null coalescing operator (??) string name = inputName ?? "Default";
// [CORRECT] Explicit null check string name; if (inputName != null) { name = inputName; } else { name = "Default"; }
// [WRONG] using declaration (C# 8.0) using FileStream stream = new FileStream(path, FileMode.Open);
// [CORRECT] using statement using (FileStream stream = new FileStream(path, FileMode.Open)) { // ... }
// [WRONG] target-typed new() List<Order> orders = new();
// [CORRECT] Explicit type List<Order> orders = new List<Order>();
// [WRONG] Async suffix public async Task<Order> GetOrderAsync(int id);
// [CORRECT] No Async suffix public async Task<Order> GetOrder(int id);
// [WRONG] inline out declaration if (int.TryParse(input, out int result))
// [CORRECT] separate out declaration int result; if (int.TryParse(input, out result))
Required Patterns
// Always use braces, even for single line if (condition) { DoSomething(); }
// Float literals with f suffix float value = 0.5f; float another = 1.0f;
// Switch must have default case switch (status) { case EStatus.Active: Process(); break; case EStatus.Inactive: Skip(); break; default: Debug.Fail("Unknown status"); break; }
// Debug.Assert for all assumptions Debug.Assert(order != null, "Order should not be null"); Debug.Assert(count > 0, "Count must be positive");
// Properties instead of getter/setter methods // [WRONG] public int GetAge() { return mAge; } public void SetAge(int age) { mAge = age; }
// [CORRECT] (.NET 5+ only - NOT available in Unity) public int Age { get; private init; }
// [CORRECT] (Unity compatible) public int Age { get; private set; }
C# 9.0 Patterns
Unity Limitation: init accessor is NOT available in Unity (requires .NET 5+ runtime). See Modern Patterns Reference for details and alternatives.
// private init (C# 9.0) - .NET 5+ only, NOT available in Unity public class Customer { public string Name { get; private init; } // Use 'private set' in Unity public string Email { get; private init; } }
// Record for immutable data public record OrderDto(int Id, string CustomerName, decimal TotalAmount);
// Pattern matching switch expression (available in Unity) public string GetStatusMessage(EOrderStatus status) { return status switch { EOrderStatus.Pending => "Order is pending", EOrderStatus.Completed => "Order completed", _ => throw new ArgumentOutOfRangeException(nameof(status)) }; }
Class Structure Order
public class OrderService : IOrderService { // 1. Constants private const int MAX_RETRY_COUNT = 3; public static readonly TimeSpan DEFAULT_TIMEOUT = TimeSpan.FromSeconds(30);
// 2. Private member variables
private readonly IOrderRepository mOrderRepository;
private readonly ILogger mLogger;
private int mProcessedCount;
private bool mbIsInitialized;
// 3. Properties (with private member above if needed)
public int ProcessedCount => mProcessedCount;
public bool IsInitialized => mbIsInitialized;
// 4. Constructors
public OrderService(IOrderRepository orderRepository, ILogger logger)
{
mOrderRepository = orderRepository;
mLogger = logger;
}
// 5. Public methods
public Order GetOrder(int id)
{
Debug.Assert(id > 0, "ID must be positive");
return mOrderRepository.Find(id);
}
public void ProcessOrder(Order order)
{
validateOrder(order);
processInternal(order);
}
// 6. Private methods
private void validateOrder(Order order)
{
Debug.Assert(order != null);
}
private void processInternal(Order order)
{
// Implementation
}
}
File Organization
-
각 클래스는 독립된 파일에 작성
-
파일명 = 클래스명 (정확히 일치)
-
Partial 클래스: ClassName.SubName.cs
Reference Documentation
Naming Conventions
Complete naming rules:
-
m/mb 접두어 상세
-
E/S 접두어 규칙
-
OrNull 접미어 패턴
-
ALL_CAPS 상수 규칙
Modern Patterns
C# 9.0 language features:
-
Records and init-only properties
-
Pattern matching
-
File-scoped namespaces (C# 10)
-
금지 패턴 상세
Error Handling
Error handling patterns:
-
Debug.Assert 사용
-
경계에서만 예외 처리
-
null 반환/매개변수 회피
-
유효성 검증 패턴
Key Principles
-
가독성 최우선: 명확하고 이해하기 쉬운 코드 작성
-
명시적 타입: var 사용 금지, 타입 명시
-
Null 안전: OrNull 접미어로 nullable 명시
-
Assertion: 모든 가정에 Debug.Assert 사용
-
경계 검증: 외부 데이터는 경계에서만 검증
-
Use init: Prefer C# 9.0 private init (.NET 5+ only, NOT available in Unity)
-
No Emoji: 코드 예제 및 문서에서 이모지 사용 금지, 텍스트 태그 사용 ([WRONG], [CORRECT], [CAUTION])