블로그로 돌아가기
C#

c# nullable reference type 실무 적용 가이드와 주의점 정리

c# nullable reference type을 통해 코드의 null 안전성을 높이고 실무에서 발생하는 널 관련 버그를 줄이는 방법을 정리합니다. 개념, 예제, 체크리스트, 주의점까지 한 번에 적용 가능합니다.

nullable-referencecsharp-8null-safetydotnetnull-forgiving-operator
C# nullable reference type을 설명하는 코드 스니펫과 널 처리 개념 이미지

c# nullable reference type이 필요한 상황

c# nullable reference type은 코드에서 널 참조(null reference)로 인한 예외를 예방하고, 컴파일 시점에 널 가능성을 표현해서 더 안전한 코드를 작성하도록 돕습니다. 이 글에서는 개념 이해, 실무 적용 예제, 체크리스트, 성능·보안·운영 관련 주의점까지 다루어 바로 프로젝트에 적용할 수 있도록 안내합니다.

c# nullable reference type 핵심 개념

C# 8.0에서 추가된 nullable reference types는 참조형식(reference type)에 대해 널 가능성(nullable)을 명시적으로 표현합니다. 기본 원칙은 다음과 같습니다.

  • 참조형 변수는 기본적으로 non-nullable로 취급됩니다.
  • 널을 허용하려면 타입 뒤에 ?를 붙여야 합니다. 예: string? name
  • 컴파일러가 널 가능성을 추적하고 경고를 제공하여 널 참조 예외를 줄입니다.

핵심 용어 정리:

  • Nullable reference: string? 처럼 널을 허용하는 참조형
  • Nullable annotations: 소스 코드에 붙이는 ?와 같은 표식
  • Null-forgiving operator: 변수! 처럼 널 검사를 생략하는 연산자

c# nullable reference type 실무 예제

아래 예제는 간단한 서비스 레이어에서 전달된 값의 널 가능성을 처리하는 방식입니다.

// C# 예제: nullable reference types 사용 #nullable enable public class UserDto { public string Id { get; set; } // non-nullable public string? DisplayName { get; set; } // nullable } public class UserService { public string GetDisplayName(UserDto user) { // 컴파일러는 user.DisplayName이 null일 수 있음을 경고 if (user.DisplayName is null) { return "(이름 없음)"; } return user.DisplayName; } } // 호출 예 var user = new UserDto { Id = "u1", DisplayName = null }; var service = new UserService(); Console.WriteLine(service.GetDisplayName(user));

심화: null-forgiving operator 사용 예

public string ForceName(UserDto user) { // 컴파일러 경고를 억제: 위험한 경우에만 사용 return user.DisplayName!; }

Nullable<T>와 참조형 Nullable의 차이

값 형식의 Nullable는 System.Nullable<T>로 표현됩니다. 참조형 nullable은 언어 레벨 어노테이션으로 널 가능성을 표현합니다.

구분항목1항목2항목3
내용값형 Nullable(T)참조형 nullable (string?)컴파일 타임 경고 제공

언제 쓰면 좋고 언제 피해야 할까

언제 쓰면 좋은가:

  • API 계약에서 파라미터/응답의 널 가능성을 명확히 표현하고 싶을 때
  • 널 관련 런타임 예외를 줄이고 정적 분석을 강화하고 싶을 때
  • 팀 차원에서 코드 품질 규칙을 통일하고자 할 때

언제 피해야 하는가:

  • 레거시 코드베이스에 도입 비용(리팩토링, 주석/어트리뷰트 수정)이 너무 클 때
  • 외부 라이브러리와 상호작용 시 널 애노테이션이 불투명해 오히려 혼란을 초래할 때
  • 짧은 스크립트나 PoC에서 빠른 구현이 더 우선인 경우

실무 체크리스트

  • 프로젝트에 #nullable enable을 단계적으로 적용할 것
  • 퍼블릭 API 설계 시 널 가능성을 문서화하고 nullable annotations 사용
  • null-forgiving operator(!)는 사용을 최소화하고 주석으로 이유를 남길 것
  • 외부 입력(HTTP, DB, 파일) 값은 항상 방어적으로 검사할 것
  • 유닛 테스트에서 널 입력 케이스를 포함할 것

성능·보안·운영 주의점

성능:

  • nullable reference types 자체는 런타임 오버헤드를 추가하지 않습니다. 이는 컴파일 타임 표시이며 JIT 코드에는 영향이 없습니다.

보안:

  • 널 검사 누락으로 인한 예외는 서비스 거부(DoS)로 이어질 수 있으므로 외부 입력 검증은 필수입니다.

운영:

  • 널 관련 경고를 무시한 채 배포하면 런타임에서 예외가 발생할 수 있으므로 CI에서 경고를 에러로 승격시키는 것을 권장합니다.

타입 설계 판단 기준표

아래 표는 타입을 nullable로 둘지 non-nullable로 둘지 결정하는 간단한 기준입니다.

구분항목1항목2항목3
내용데이터 출처(내부/외부)기본값 유무nullable로 설정 여부
내용외부 입력기본값 없음 -> 검증 필요nullable 권장
내용내부 생성 값생성자에서 보장됨non-nullable 권장

마이그레이션 전략(단계별 적용)

  1. 컴파일러 옵션으로 nullable 기능 켜기 (#nullable enable 또는 csproj에서 Nullable 참조 활성화).
  2. 경고 목록을 검토하고 우선순위가 높은 경고부터 수정.
  3. 퍼블릭 API와 DTO부터 주석 달기 및 수정.
  4. 내부 코드를 점진적으로 수정하고 CI에서 경고를 모니터링.

c# nullable reference type 도입 시 흔한 실수와 회피 방법

  • 실수: null-forgiving operator를 남발하여 컴파일러 경고를 무시함. 회피: 주석으로 이유를 기록하고, 리팩토링으로 제거 목표 설정.
  • 실수: 외부 데이터에 대해 nullable을 과도하게 신뢰함. 회피: 방어적 검증과 테스트 케이스 추가.

자주 묻는 질문

Q1: nullable reference type을 켜면 기존 코드가 모두 깨지나요? A1: 모든 코드가 깨지진 않지만 많은 경고가 발생할 수 있어 단계적 적용과 우선순위 기반 수정이 필요합니다.

Q2: null-forgiving operator(!)는 언제 사용해도 안전한가요? A2: 안전하지 않습니다. 컴파일러 경고를 억제하므로 실제 널이 들어올 가능성이 없음을 확신할 때만 제한적으로 사용하세요.

Q3: 외부 라이브러리의 null 애노테이션이 없으면 어떻게 처리하나요? A3: 어댑터 레이어에서 널 검증을 수행하거나, nullable annotations을 어댑터에 적용해 경계를 명확히 하세요.

Q4: Nullable<T>와 string? 중 어떤 것을 언제 사용하나요? A4: Nullable<T>는 값형에 사용하고 string? 등 ? 표기는 참조형에 사용합니다. 목적에 따라 타입을 선택하세요.

정리

  • c# nullable reference type은 널 안전성을 컴파일 타임에 향상시킵니다.
  • 단계적 도입과 CI에서 경고 관리는 필수입니다.
  • null-forgiving operator 사용은 최소화하고 이유를 문서화하세요.
  • 외부 입력은 항상 방어적으로 검증해야 합니다.
  • 체크리스트를 통해 도입과 운영을 체계적으로 진행하세요.