Favicon

Null conditional operators

Peponi12/29/20243m

C#
SyntaxOperator?.?[]

1. Introduction

Null 조건부 연산자 (?., ?[]) 는 멤버 액세스, 인덱서 액세스 시 피연산자가 null이 아닐 때만 연산을 수행하며 null인 경우에는 null을 반환한다.

2. Example

Null 조건부 연산자는 단락 연산자로 피연산자가 null인 경우 .[] 연산이 실행되지 않고 null을 반환한다.

string? foo = null;
 
if (foo?.Length is null) Console.WriteLine("Null-conditional member access returns null");
if (foo?[1] is null) Console.WriteLine("Null-conditional indexer access returns null");
 
/* output:
Null-conditional member access returns null
Null-conditional indexer access returns null
*/

피연산자가 null이 아닌 경우, ?.?[]는 각각 .[] 연산의 결과를 반환한다.

string? foo = "ABCDE";
 
if (foo?.Length is int length)
    Console.WriteLine(length);
else
    Console.WriteLine("Null-conditional member access returns null");
 
if (foo?[1] is char value)
    Console.WriteLine(value);
else
    Console.WriteLine("Null-conditional indexer access returns null");
 
/* output:
5
B
*/

피연산자의 멤버 또는 인덱서가 T 형식인 경우, ?.X, ?[X]T? 형식이다. Null 조건부 연산을 사용해 T 형식을 얻으려는 경우 null 병합 연산자를 식에 넣어준다.

public class Foo
{
    public int Bar = 1;
}
 
Foo? foo = new();
var bar = foo?.Bar;
 
PrintType(bar);
PrintType(bar ?? 0);
 
void PrintType<T>(T value) => Console.WriteLine($"{typeof(T)}");
 
/* output:
System.Nullable`1[System.Int32]
System.Int32
*/

Null 조건부 연산자는 괄호 등으로 인해 단락 연산자의 성질을 잃게 되는 경우 예기치 않은 오류를 일으킬 수 있다.

public class Foo
{
    public Bar FooMember = new();
 
    public class Bar
    {
        public int BarMember = 1;
    }
}
 
Foo? foo = null;
 
var bar = foo?.FooMember.BarMember;     // null
 
var baz = (foo?.FooMember).BarMember;   // NullReferenceException

3. 안전한 대리자 호출

?. 연산자는 대리자에 대한 null 체크 및 thread-safe 호출을 지원한다.

public static EventHandler? Foo;
 
Foo?.Invoke(null, new());

위 예제의 Foo?.Invoke()는 런타임에 다음과 같이 동작하여 thread-safe 하게 된다.

var delegateExpression = Foo;
if (delegateExpression is not null) delegateExpression?.Invoke(null, new());

delegate를 미리 복사하여 null 체크와 delegate invoke 사이에 Foo가 변경되더라도 안전한 동작이 보장된다.

4. 참조 자료