본문 바로가기

진리는어디에/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. 같이 읽으면 좋은 글

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