본문 바로가기

진리는어디에/C#

[C#] System.Index

이번 포스트에서는 C# 8.0에서 추가된 인덱스(index)라는 개념에 대해 살펴 보도록 하겠다.

들어가며..

Index에 대한 설명에 앞서 아래 예제를 보자. 특히 10라인에 ^ 기호가 사용 된 부분을 주목하자.

using System; class Program { ​​​​static void Main(string[] args) ​​​​{ ​​​​​​​​int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; ​​​​​​​​int n1 = arr[2]; ​​​​​​​​int n2 = arr[^2]; // 뒤에서 부터 접근 ​​​​​​​​Console.WriteLine($"{n1}, {n2}"); // 3, 9 ​​​​} }

10개짜리 정수 배열을 만들고 인덱스를 이용해 접근하는 간단한 예제다.

먼저 9라인을 보면 인덱스 2의 위치에 있는 요소에 접근한다. C#은 0부터 인덱스를 시작하므로 인덱스가 2라면 실제로 세번째 요소 '3'에 접근한다.

다시 10라인을 주목하자. 인덱스 앞에 ^가 붙었다. 이것은 C# 8.0 부터 추가 된 문법으로써 뒤에서 부터 접근하라는 의미다. 여기서 주의할 것이 뒤에서 부터 접근 할 때는 1부터 시작한다는 것이다.

^2라는 것은 뒤에서 두번째 즉 9가 된다. 여기까지는 어렵지 않다. 그냥 배열에 인덱스를 이용해 접근하는데 뒤에서 부터 접근하는 방법도 있다라는 정도만 알아 두면 된다.

^를 사용하면 배열의 뒤에서 부터 앞으로 접근한다.

이번에는 배열말고 문자열의 각 요소에 접근해보도록 하자. 문자열 역시 각 요소에 접근 할 때 배열 연산을 이용 할 수 있다. 문자열의 요소는 문자이므로 char 타입 에 결과를 저장 할 수 있다.

using System; class Program { ​​​​static void Main(string[] args) ​​​​{ ​​​​​​​​string s = "ABCDEFGHI"; ​​​​​​​​// 시퀀스 요소에 접근하기 위한 인덱스 만들기 ​​​​​​​​int index1 = 2; ​​​​​​​​char ch1 = s[index1]; ​​​​​​​​Console.WriteLine($"{ch1}"); // C ​​​​} }

여기서 부터 중요하다. 일반적으로 배열 연산을 통해 접근 할 때 int 타입을 이용해 인덱스를 만들게 된다. 하지만 C# 8.0 부터 Index라는 타입이 새로 생겼다.

기존의 int형 인덱스 대신에 이제 Index라는 타입을 사용하여 똑같이 요소에 접근 할 수 있다.

using System; class Program { ​​​​static void Main(string[] args) ​​​​{ ​​​​​​​​string s = "ABCDEFGHI"; ​​​​​​​​// 시퀀스 요소에 접근하기 위한 인덱스 만들기 ​​​​​​​​int index1 = 2; ​​​​​​​​char ch1 = s[index1]; ​​​​​​​​Index index2 = new Index(2); ​​​​​​​​char ch2 = s[index2]; ​​​​​​​​Console.WriteLine($"{ch1}, {ch2}"); // C, C ​​​​} }

"어? 그냥 int를 사용하면 되는데 왜 Index를 써야 하는가?"라는 의문이 들수도 있다.

int를 사용하면 시퀀스 요소에 접근하기 위한 값만을 보관 할 수 있다. 하지만 System.Index를 사용하면 값과 방향을 같이 보관 할 수 있다. 그냥  Index 객체만을 생성한다면 앞에서 부터 접근하지만 두번째 인자로 true을 넘기면 뒤에서 부터 접근하는 인덱스를 만들 수 있다.

​​​​​​​​Index index2 = new Index(2); ​​​​​​​​char ch2 = s[index2]; ​​​​​​​​ ​​​​​​​​Index index3 = new Index(2, true); ​​​​​​​​char ch3 = s[index3]; ​​​​​​​​Console.WriteLine($"{ch1}, {ch2}, {ch3}"); // C, C, H

"ABCDEFGHI"의 뒤에서 두번째 요소인 H가 출력 된다. 그런데 위와 같이 그냥 true만 적으면 헷깔릴 수 있으니 C# 메쏘드에 인자의 이름도 넘길 수 있는 기능을 이용해 new Index(2, fromEnd:true) 와 같이 적어 주면 가독성이 더 좋다.

Index 객체 만드는 방법

  • new 사용
Index idx1 = new Index(3); Index idx2 = new Index(3, fromEnd : true);
  • 정적 메소드 사용
Index idx3 = Index.FromStart(3); Index idx4 = Index.FromEnd(3);
  • 단축 표기법 사용
Index idx5 = 3; Index idx6 = ^3;

앞의 제일 첫번째 예제에서 우리가 int n2 = arr[^2]라고 적은 것은 결국 컴파일러가  arr[new Index(2, fromEnd:true)] 처럼 변경해준다.

사용자 정의 타입에 Index 적용

먼저 아래의 코드를 살펴 보자. 아래 Sentence 클래스는 문장을 입력 받아 공백을 기준으로 단어별로 쪼개 배열에 저장한다. 그리고 인덱서 프로퍼티를 구현해 각 단어를 인덱스를 이용해 접근 할 수 있게 했다.

public class Sentence { ​​​​private string[] words; ​​​​public Sentence(string sentence) ​​​​{ ​​​​​​​​words = sentence.Split(); ​​​​} ​​​​public string this[int index] ​​​​{ ​​​​​​​​get { return words[index]; } ​​​​} } class Program { ​​​​static void Main(string[] args) ​​​​{ ​​​​​​​​Sentence sentence = new Sentence("Sometimes bad things happen to good people"); ​​​​​​​​Console.WriteLine($"{sentence[2]}"); // things 출력 ​​​​} }

그럼 위 Sentencs를 Index를 이용해 접근하면 어떻게 될까?

class Program { ​​​​static void Main(string[] args) ​​​​{ ​​​​​​​​Sentence sentence = new Sentence("Sometimes bad things happen to good people"); ​​​​​​​​Console.WriteLine($"{sentence[2]}"); // things 출력 ​​​​​​​​Console.WriteLine($"{sentence[^2]}"); // 오류 CS1503 1 인수: 'System.Index'에서 'int'(으)로 변환할 수 없습니다. ​​​​} }

위 예제를 컴파일하면 System.Index에서 int로 변경 할 수 없다는 컴파일 에러가 발생한다. 컴파일러는 인덱스를 사용하는 코드를 만나면 아래와 같이 3가지 순서로 컴파일을 시도한다.

  1. "인덱서" 프로퍼티가 있는지 찾는다
    컴파일러는 해당 객체에 this[Index] 프로퍼티가 구현 되어 있는지 찾는다.
  2. "Length" 프로퍼티가 있는지 찾는다
    만일 Lengh 프로퍼티가 있다면 앞에서 부터 접근한다면 sentence[sentence.Length], 뒤에서 부터 접근한다면 sentence[sentence.Length - N]과 같이 코드를 변경한다.
  3. "Count" 프로퍼티가 있는지 찾는다
    만일 Count 프로퍼티가 있다면 앞에서 부터 접근한다면 sentence[sentence.Count], 뒤에서 부터 접근한다면 sentence[sentence.Count - N]과 같이 코드를 변경한다.

하지만 위 Sentence 클래스에는 위 세가지에 해당하는 아무런 프로퍼티도 없으므로 컴파일에 실패하게 된다. 다음과 같이 Sentence 클래스에 프로퍼티들을 추가해보자.

public class Sentence { ​​​​private string[] words; ​​​​public Sentence(string sentence) ​​​​{ ​​​​​​​​words = sentence.Split(); ​​​​} ​​​​public string this[int index] ​​​​{ ​​​​​​​​get { return words[index]; } ​​​​} ​​​​// "인덱서" 프로퍼티 ​​​​public string this[Index index] ​​​​{ ​​​​​​​​get { return words[index]; } ​​​​} ​​​​// "Length" 프로퍼티 ​​​​public int Length { get { return words.Length; } } ​​​​// "Count" 프로퍼티 ​​​​public int Count { get { return words.Length ; } } }

위 수정 사항을 적용하고 나면, 이제는 정상적으로 컴파일 되고 결과가 출력 되는 것을 확인 할 수 있다.

마치며

이상 C# 8.0 부터 추가된 Index 에 대해 알아 보았다.

  • 인덱스에 ^를 사용하면 배열의 뒤에서 부터 앞으로 접근한다.
  • Index는 인덱스와 방향을 저장한다.
  • Index 생성자의 두번째 인자에 true를 넘기면 뒤에서 부터 접근하는 Index다.
  • 가독성을 높이기 위해 fromEnd 인자 이름을 사용하면 더 좋다.
  • Index를 사용자 정의 타입에 사용하면 컴파일러는 인덱서 프로퍼티 -> Length 프로퍼티 -> Count 프로퍼티 순으로 검색하여 코드를 변경한다.

부록 1. 같이 읽으면 좋은 글

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!