이번 포스트에서는 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가지 순서로 컴파일을 시도한다.
- "인덱서" 프로퍼티가 있는지 찾는다
컴파일러는 해당 객체에 this[Index] 프로퍼티가 구현 되어 있는지 찾는다. - "Length" 프로퍼티가 있는지 찾는다
만일 Lengh 프로퍼티가 있다면 앞에서 부터 접근한다면 sentence[sentence.Length], 뒤에서 부터 접근한다면 sentence[sentence.Length - N]과 같이 코드를 변경한다. - "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 프로퍼티 순으로 검색하여 코드를 변경한다.