null 허용 연산자를 현업에서는 어떻게 사용 하시는지 질문드립니다.

독학으로 공부 해 오면서 최근부터 c# 버전을 업을 했더니 가능한 null 참조반환이라는 경고가 떠서 검색해봤더니 null 허용 연산자 ?, ?? 을 사용하면 경고가 없어지긴 했습니다. 현업에서는 어떻게 사용하고 처리하시는지 궁금하더군요.

이런 코드 예시를 가져와봤습니다.
멤버변수나 매개변수에 ?연산자를 붙여봤지만 리턴하거나 다른 함수에 대입될땐 어김없이 경고가 뜨네요
어떻게들 사용하고 계신지요? 안쓰시는분도 계신가요??

private string? _password;
[Required(ErrorMessage = “Password is Required”)]
public string Password
{
get { return _password; }
set
{
_name = value;
Validate(nameof(Password), value);
}
}

public IEnumerable GetErrors(string? propertyName)
{

if (Errors.TryGetValue(**propertyName**, out List<string>? value))
{
    return value;
}
else
{
    return Enumerable.Empty<string>();
}

}

public void Validate(string? propertyName, object propertyValue)
{
var results = new List();
Validator.TryValidateProperty(propertyValue, new ValidationContext(this) { MemberName = propertyName}, results);

if (results.Any())
{
    Errors.Add(propertyName, **results.Select(x => x.ErrorMessage).ToList()**);
    ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
else
{
    Errors.Remove(propertyName);
    ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}

}

3 Likes

GPT4.5로 돌려 봤습니다 :rofl:

좋은 질문 감사합니다. C#에서 nullable reference types가 도입되면서 많은 개발자들이 혼란을 겪고 있는 부분인데요, 아래에 관련 개념 정리와 함께 현업에서는 어떻게 활용되고 있는지, 그리고 주신 코드의 문제점과 개선 방향까지 정리해서 설명드릴게요.


:mag: Nullable Reference Type 개요

C# 8.0부터 도입된 nullable reference types 기능은 참조형 변수에도 ?를 붙여서 null을 허용할지 말지를 명시적으로 표현할 수 있도록 한 기능입니다.

  • string? : null을 허용하는 문자열
  • string : null을 허용하지 않는 문자열

이 기능이 활성화되면, null이 할당될 가능성이 있는 모든 경우에 경고가 발생합니다. 의도하지 않은 NullReferenceException을 방지하고자 하는 목적이죠.


:gear: 현업에서의 사용 방식

:white_check_mark: 일반적인 처리 흐름

현업에서는 다음과 같은 방식으로 nullable 경고를 처리합니다:

  1. 의도적으로 null이 가능한 경우
    string?, List<string>? 와 같이 선언
    → 이후 사용 시 null 검사 또는 ??, ?. 등의 연산자 사용

  2. 절대 null이 될 수 없는 경우
    ! 연산자 (null-forgiving operator)로 컴파일러에 "이건 절대 null 아님"이라고 명시적으로 알려줌
    → 단, 이건 안전하지 않기 때문에 정말 확신할 때만 사용

  3. Validation 패턴 사용
    → 외부에서 입력받은 값은 항상 null 체크 후 내부에서 non-null 변수에 할당

  4. ViewModel이나 DTO에서는 최대한 string?을 적극 활용
    → 특히 WPF, WinForms, Blazor 등에서 INotifyDataErrorInfo 활용 시 필수적으로 null 허용 선언


:warning: 주신 코드의 문제점 분석

문제 1: _password는 nullable인데, Password getter에서 경고 발생 가능

private string? _password;
public string Password => _password;

여기서 _password가 null일 수 있으니, 반환 시 경고가 발생할 수 있습니다.

해결 방법:

  1. null 반환이 논리적으로 불가능하다면 ! 사용: return _password!;
  2. 아니면 Password 자체를 nullable로 선언: public string? Password => _password;
  3. 혹은 초기값 보장: _password = ""

문제 2: GetErrors 메서드에서 propertyName null 가능

if (Errors.TryGetValue(propertyName, out List<string>? value))

propertyName이 null이면 TryGetValue에서 ArgumentNullException 발생 가능.
또한 이후에 new DataErrorsChangedEventArgs(propertyName)도 예외 발생 위험 있음.

해결 방법:

if (string.IsNullOrEmpty(propertyName)) return Enumerable.Empty<string>();

문제 3: Errors.Add(...)에서 propertyName이 null이면 예외 발생

해결 방법:

if (string.IsNullOrEmpty(propertyName)) return;

:white_check_mark: 개선 예시

private string? _password;
[Required(ErrorMessage = "Password is Required")]
public string Password
{
    get => _password ?? "";
    set
    {
        _password = value;
        Validate(nameof(Password), value);
    }
}

public IEnumerable GetErrors(string? propertyName)
{
    if (string.IsNullOrEmpty(propertyName)) return Enumerable.Empty<string>();

    return Errors.TryGetValue(propertyName, out var value)
        ? value
        : Enumerable.Empty<string>();
}

public void Validate(string? propertyName, object propertyValue)
{
    if (string.IsNullOrEmpty(propertyName)) return;

    var results = new List<ValidationResult>();
    Validator.TryValidateProperty(propertyValue,
        new ValidationContext(this) { MemberName = propertyName }, results);

    if (results.Any())
    {
        Errors[propertyName] = results.Select(x => x.ErrorMessage ?? "Unknown Error").ToList();
    }
    else
    {
        Errors.Remove(propertyName);
    }

    ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}

:bulb: 마무리 팁

  • Nullable 기능은 null을 방지하려는 의도를 명확히 드러내는 장치입니다.
  • 모든 경고를 없애는 것보다는, 의미 있는 경고만 남기고 명확한 처리 루틴을 가지는 것이 중요합니다.
  • 현업에서는 ?, ??, ?., !, null 체크를 적절히 조합하여 사용하고 있습니다.

필요하시면 ViewModel 또는 ASP.NET Core 프로젝트에서 어떻게 null 허용 타입을 구성하는지도 예시로 설명드릴 수 있어요. 추가로 궁금한 점 있으시면 언제든 질문 주세요!

1 Like

프로젝트마다 다른데, 프로젝트 설정에서 끌 수 있습니다.
아니면 파일 별로 #nullable 적용해주시면 됩니다.

null 컨텍스트 기능은 null 의 전파 경로를 명확하게 드러내 주기 때문에, 반드시 사용하는 편입니다.

이 기능을 도입하면, 내 코드로 인해 null 예외가 발생하는 경우는 거의 없다고 보시면 됩니다.

null 컨텍스트 기능을 적용한 후에, null 경고가 나타났다면, 그 수 만큼, 과거 코드에 null 예외 발생 가능성이 높았다는 의미입니다.

그러나, 프레임워크나 외부 라이브러리 등 외부 코드가 null 을 전달할 수도 있기 때문에 완전히 사라지는 것은 아닙니다.
다만, 발생 지점이 뻔해져 대처가 쉬워집니다.