다음을 통해 공유


멤버 액세스 연산자 및 식, 점, 인덱서 및 호출 연산자입니다.

여러 연산자와 식을 사용하여 형식 멤버에 액세스합니다. 이러한 연산자에는 멤버 액세스(.), 배열 요소 또는 인덱서 액세스([]), index-from-end(^), 범위(..), null 조건부 연산자(?.?[]), 메서드 호출(())이 포함됩니다. 여기에는 null 조건부 멤버 액세스(?.) 및 인덱서 액세스(?[]) 연산자가 포함됩니다.

멤버 액세스 식 .

다음 예제와 같이 . 토큰을 사용하여 네임스페이스 또는 형식의 멤버에 액세스합니다.

  • using 지시문의 다음 예제와 같이 .을 사용하여 네임스페이스 내에 중첩된 네임스페이스에 액세스합니다.
using System.Collections.Generic;
  • 다음 코드와 같이 .을 사용하여 ‘정규화된 이름’을 만들고 네임스페이스 내의 형식에 액세스합니다.
System.Collections.Generic.IEnumerable<int> numbers = [1, 2, 3];

using 지시문을 사용하여 정규화된 이름 사용을 선택 사항으로 설정합니다.

  • 다음 코드와 같이 .을 사용하여 정적 및 비정적 형식 멤버에 액세스합니다.
List<double> constants =
[
    Math.PI,
    Math.E
];
Console.WriteLine($"{constants.Count} values to show:");
Console.WriteLine(string.Join(", ", constants));
// Output:
// 2 values to show:
// 3.14159265358979, 2.71828182845905

.을 사용하여 확장 메서드에 액세스할 수도 있습니다.

인덱서 연산자 []

대괄호 []는 일반적으로 배열, 인덱서 또는 포인터 요소 액세스에 사용됩니다. C# 12부터 []컬렉션 식을 묶습니다.

배열 액세스

다음 예제는 배열 요소에 액세스하는 방법을 보여 줍니다.

int[] fib = new int[10];
fib[0] = fib[1] = 1;
for (int i = 2; i < fib.Length; i++)
{
    fib[i] = fib[i - 1] + fib[i - 2];
}
Console.WriteLine(fib[fib.Length - 1]);  // output: 55

double[,] matrix = new double[2,2];
matrix[0,0] = 1.0;
matrix[0,1] = 2.0;
matrix[1,0] = matrix[1,1] = 3.0;
var determinant = matrix[0,0] * matrix[1,1] - matrix[1,0] * matrix[0,1];
Console.WriteLine(determinant);  // output: -3

배열 인덱스가 배열의 해당 차원 범위를 벗어난 경우 IndexOutOfRangeException이 throw됩니다.

앞의 예제와 같이, 배열 형식을 선언하거나 배열 인스턴스를 인스턴스화할 때도 대괄호를 사용합니다.

배열에 대한 자세한 내용은 배열을 참조하세요.

인덱서 액세스

다음 예제는 .NET Dictionary<TKey,TValue> 형식을 사용하여 인덱서 액세스를 보여 줍니다.

var dict = new Dictionary<string, double>();
dict["one"] = 1;
dict["pi"] = Math.PI;
Console.WriteLine(dict["one"] + dict["pi"]);  // output: 4.14159265358979

인덱서를 사용하면 배열 인덱싱과 비슷한 방법으로 사용자 정의 형식의 인스턴스를 인덱싱할 수 있습니다. 정수여야 하는 배열 인덱스와 달리, 인덱서 매개 변수는 임의 형식으로 선언할 수 있습니다.

인덱서에 대한 자세한 내용은 인덱서를 참조하세요.

다른 [] 용도

포인터 요소 액세스에 대한 자세한 내용은 포인터 관련 연산자 문서의 포인터 요소 액세스 연산자 섹션을 참조하세요. 컬렉션 식에 대한 내용은 컬렉션 식 문서를 참조하세요.

또한 대괄호를 사용하여 특성을 지정합니다.

[System.Diagnostics.Conditional("DEBUG")]
void TraceMethod() {}

Null 조건부 연산자 ?.?[]

null 조건부 연산자는 피연산자가 null이 아닌 것으로 평가되었을 때만 멤버 액세스, ?. 또는 요소 액세스, ?[], 연산을 피연산자에게 적용하며, 그렇지 않으면 null을 반환합니다. 위의 명령이 반환하는 결과는 다음과 같습니다.

  • anull로 평가되면 a?.x 또는 a?[x]의 결과는 null입니다.

  • a가 null이 아닌 것으로 평가되면 a?.x 또는 a?[x]의 결과는 각각 a.x 또는 a[x]의 결과와 같습니다.

    참고 항목

    a.x 또는 a[x]가 예외를 throw하면 a?.x 또는 a?[x]는 null이 아닌 a와 동일한 예외를 throw합니다. 예를 들어 a가 null이 아닌 배열 인스턴스이고 xa의 경계 밖에 있는 경우, a?[x]IndexOutOfRangeException을 throw합니다.

Null 조건부 연산자는 단락 연산자입니다. 즉 조건부 멤버나 요소 액세스 작업의 한 체인의 작업에서 null을 반환하면 나머지 체인은 실행되지 않습니다. 다음 예제에서 Anull로 평가되면 B가 평가되지 않고, A 또는 Bnull로 평가되면 C가 평가되지 않습니다.

A?.B?.Do(C);
A?.B?[C];

A가 Null일 수 있지만 A가 Null이 아닌 경우 BC는 Null이 되지 않는다면 A에만 Null 조건부 연산자를 적용하면 됩니다.

A?.B.C();

앞의 예제에서 B은(는) 평가되지 않으며 A이(가) null인 경우 C()이(가) 호출되지 않습니다. 그러나 연결된 멤버 액세스가 중단될 경우(예: (A?.B).C()처럼 괄호에 의해) 단락이 발생하지 않습니다.

다음 예제에서는 ?.?[] 연산자의 사용법을 보여 줍니다.

double SumNumbers(List<double[]> setsOfNumbers, int indexOfSetToSum)
{
    return setsOfNumbers?[indexOfSetToSum]?.Sum() ?? double.NaN;
}

var sum1 = SumNumbers(null, 0);
Console.WriteLine(sum1);  // output: NaN

List<double[]?> numberSets =
[
    [1.0, 2.0, 3.0],
    null
];

var sum2 = SumNumbers(numberSets, 0);
Console.WriteLine(sum2);  // output: 6

var sum3 = SumNumbers(numberSets, 1);
Console.WriteLine(sum3);  // output: NaN
namespace MemberAccessOperators2;

public static class NullConditionalShortCircuiting
{
    public static void Main()
    {
        Person? person = null;
        person?.Name.Write(); // no output: Write() is not called due to short-circuit.
        try
        {
            (person?.Name).Write();
        }
        catch (NullReferenceException)
        {
            Console.WriteLine("NullReferenceException");
        }; // output: NullReferenceException
    }
}

public class Person
{
    public required FullName Name { get; set; }
}

public class FullName
{
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
    public void Write() => Console.WriteLine($"{FirstName} {LastName}");
}

앞의 두 예제 중 첫 번째 예제에서는 null 병합 연산자 ??를 사용하여 null 조건부 연산의 결과가 null인 경우 평가할 대체 식을 지정합니다.

a.x 또는 a[x]가 null을 허용하지 않는 값 형식인 경우 T, a?.x 또는 a?[x]는 해당하는 null 허용 값 형식 T?입니다. T 형식의 식이 필요하면 다음 예제와 같이 null 병합 연산자 ??를 null 조건식에 적용합니다.

int GetSumOfFirstTwoOrDefault(int[]? numbers)
{
    if ((numbers?.Length ?? 0) < 2)
    {
        return 0;
    }
    return numbers[0] + numbers[1];
}

Console.WriteLine(GetSumOfFirstTwoOrDefault(null));  // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault([]));  // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault([3, 4, 5]));  // output: 7

앞의 예제에서 ?? 연산자를 사용하지 않는 경우 numbers?.Length < 2numbersnull일 때 false로 평가됩니다.

참고 항목

?. 연산자는 왼쪽 피연산자를 한 번만 계산하여 null이 아닌 것으로 확인된 후에는 null로 변경할 수 없도록 보장합니다.

Null 조건부 멤버 액세스 연산자 ?.를 Elvis 연산자라고도 합니다.

스레드로부터 안전한 대리자 호출

다음 코드에서처럼 ?. 연산자를 사용하여 대리자가 null이 아닌지 확인하고 스레드로부터 안전한 방식으로 호출합니다(예: 이벤트 발생 시).

PropertyChanged?.Invoke(…)

해당 코드는 다음 코드와 동일합니다.

var handler = this.PropertyChanged;
if (handler != null)
{
    handler(…);
}

앞의 예제는 null이 아닌 handler만 호출되도록 하는 스레드로부터 안전한 방법입니다. 대리자 인스턴스는 변경할 수 없으므로 스레드는 handler 지역 변수가 참조하는 개체를 변경할 수 없습니다. 특히 다른 스레드가 실행한 코드가 PropertyChanged 이벤트에서 구독을 취소하고 handler를 호출하기 전에 PropertyChangednull이 되면 handler에서 참조하는 개체는 영향을 받지 않습니다.

호출 식 ()

괄호(())를 사용하여 메서드 또는 대리자를 호출합니다.

다음 예제는 인수를 사용하거나 사용하지 않고 메서드를 호출하는 방법과 대리자를 호출하는 방법을 보여 줍니다.

Action<int> display = s => Console.WriteLine(s);

List<int> numbers =
[
    10,
    17
];
display(numbers.Count);   // output: 2

numbers.Clear();
display(numbers.Count);   // output: 0

new 연산자를 사용하여 생성자를 호출하는 경우에도 괄호를 사용합니다.

다른 () 용도

또한 괄호를 사용하여 식에서 연산을 계산하는 순서를 조정합니다. 자세한 내용은 C# 연산자를 참조하세요.

명시적 형식 변환을 수행하는 캐스트 식도 괄호를 사용합니다.

끝부터 인덱스 연산자 ^

인덱스와 범위 연산자는 셀 수 있는 형식과 함께 사용할 수 있습니다. 셀 수 있는 형식은 액세스 가능한 get 접근자가 있으며 이름이 Count 또는 Lengthint 속성이 있는 형식입니다. 컬렉션 식셀 수 있는 형식에도 의존합니다.

^ 연산자는 시퀀스의 끝에서 요소 위치를 나타냅니다. 시퀀스 길이 length의 경우 ^n은 시퀀스의 시작에서 오프셋 length - n인 요소를 가리킵니다. 예를 들어 ^1은 시퀀스의 마지막 요소를 가리키고, ^length는 시퀀스의 첫 번째 요소를 가리킵니다.

int[] xs = [0, 10, 20, 30, 40];
int last = xs[^1];
Console.WriteLine(last);  // output: 40

List<string> lines = ["one", "two", "three", "four"];
string prelast = lines[^2];
Console.WriteLine(prelast);  // output: three

string word = "Twenty";
Index toFirst = ^word.Length;
char first = word[toFirst];
Console.WriteLine(first);  // output: T

위 예제에서와 같이 식 ^eSystem.Index 형식입니다. 식 ^e에서 e의 결과는 암시적으로 int으로 변환할 수 있어야 합니다.

^ 연산자를 범위 연산자와 함께 사용하여 인덱스 범위를 만들 수도 있습니다. 자세한 내용은 인덱스와 범위를 참조하세요.

C# 13부터 End 연산자의 인덱스를 개체 이니셜라이저에 사용할 수 있습니다.

범위 연산자 ..

.. 연산자는 인덱스 범위의 시작과 끝을 피연산자로 지정합니다. 왼쪽 피연산자는 범위의 시작(포함)입니다. 오른쪽 피연산자는 범위의 끝(제외)입니다. 다음 예제에서와 같이 피연산자 중 하나는 시퀀스의 시작부터 또는 끝부터 인덱스가 될 수 있습니다.

int[] numbers = [0, 10, 20, 30, 40, 50];
int start = 1;
int amountToTake = 3;
int[] subset = numbers[start..(start + amountToTake)];
Display(subset);  // output: 10 20 30

int margin = 1;
int[] inner = numbers[margin..^margin];
Display(inner);  // output: 10 20 30 40

string line = "one two three";
int amountToTakeFromEnd = 5;
Range endIndices = ^amountToTakeFromEnd..^0;
string end = line[endIndices];
Console.WriteLine(end);  // output: three

void Display<T>(IEnumerable<T> xs) => Console.WriteLine(string.Join(" ", xs));

위 예제에서와 같이 식 a..bSystem.Range 형식입니다. 식 a..b에서 ab의 결과는 암시적으로 Int32 또는 Index로 변환할 수 있어야 합니다.

Important

int에서 Index로의 암시적 변환은 값이 음수일 경우 ArgumentOutOfRangeException을 throw합니다.

.. 연산자의 피연산자 중 하나를 생략하여 개방형 범위를 지정할 수 있습니다.

  • a..a..^0와 같습니다.
  • ..b0..b와 같습니다.
  • ..0..^0와 같습니다.
int[] numbers = [0, 10, 20, 30, 40, 50];
int amountToDrop = numbers.Length / 2;

int[] rightHalf = numbers[amountToDrop..];
Display(rightHalf);  // output: 30 40 50

int[] leftHalf = numbers[..^amountToDrop];
Display(leftHalf);  // output: 0 10 20

int[] all = numbers[..];
Display(all);  // output: 0 10 20 30 40 50

void Display<T>(IEnumerable<T> xs) => Console.WriteLine(string.Join(" ", xs));

다음 표에서는 컬렉션 범위를 표현하는 다양한 방법을 보여 줍니다.

범위 연산자 식 설명
.. 컬렉션의 모든 값입니다.
..end 처음부터 end까지의 값입니다.
start.. start부터 끝까지의 값입니다.
start..end start부터 end까지의 값입니다.
^start.. start부터 끝까지의 값(끝에서 계산)입니다.
..^end 시작부터 end까지의 값(끝부터 계산)입니다.
start..^end start부터 end까지의 값(끝부터 계산)입니다.
^start..^end start부터 end까지의 값(둘 다 끝부터 계산)입니다.

다음 예제에서는 앞의 표에 표시된 모든 범위를 사용하는 효과를 보여 줍니다.

int[] oneThroughTen =
[
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10
];

Write(oneThroughTen, ..);
Write(oneThroughTen, ..3);
Write(oneThroughTen, 2..);
Write(oneThroughTen, 3..5);
Write(oneThroughTen, ^2..);
Write(oneThroughTen, ..^3);
Write(oneThroughTen, 3..^4);
Write(oneThroughTen, ^4..^2);

static void Write(int[] values, Range range) =>
    Console.WriteLine($"{range}:\t{string.Join(", ", values[range])}");
// Sample output:
//      0..^0:      1, 2, 3, 4, 5, 6, 7, 8, 9, 10
//      0..3:       1, 2, 3
//      2..^0:      3, 4, 5, 6, 7, 8, 9, 10
//      3..5:       4, 5
//      ^2..^0:     9, 10
//      0..^3:      1, 2, 3, 4, 5, 6, 7
//      3..^4:      4, 5, 6
//      ^4..^2:     7, 8

자세한 내용은 인덱스와 범위를 참조하세요.

.. 토큰은 컬렉션 식의 spread 요소에도 사용됩니다.

연산자 오버로드 가능성

., (), ^.. 연산자는 오버로드할 수 없습니다. [] 연산자도 오버로드할 수 없는 연산자로 간주됩니다. 인덱서를 사용하여 사용자 정의 형식의 인덱싱을 지원합니다.

C# 언어 사양

자세한 내용은 C# 언어 사양의 다음 섹션을 참조하세요.

인덱스 및 범위에 대한 자세한 내용은 기능 제안 노트를 참조하세요.

참고 항목