본문 바로가기

C# 공부/C# 기본 문법

인덱서 (인덱서의 선언과 사용, foreach가 가능한 객체 만들기)

A. 인덱서(Indexer)의 선언

인덱서(Indexer)는 인덱스를 이용해서 객체 내의 데이터에 접근하게 해주는 프로퍼티이며, 프로퍼티와 인덱서의 차이점은 인덱스를 사용하여 객체를 배열처럼 사용하게 해주는것이다.

아래는 인덱서의 선언 예시이며, MyList는 내부에 정수형식 배열을 갖고, 인덱서를 통해 이 배열에 접근한다. 인덱서를 통해 데이터를 저장하고자 하는 시도가 이뤄질때 지정한 인덱스보다 배열의 크기가 작다면 인덱스에 맞춰 배열의 크기를 재조정한다.

class MyList
{
    private int[] array;
    
    public MyList()
    {
        array = new int[3];
    }
    
    public int this[int index] //인덱서
    {
        get
        {
        	return array[index];
        }
        
        set
        {
            if(index >= array.Length)
            {
                Array.Resize<int>(ref array, index+1);
                Console.WriteLine("Array Resized :{0}",array.Length);
            }
            
            array[index] = value;
        }
    }
    
    public int Length
    {
    	get{ return array.Length; }
    }
}

class MainApp
{
    static void Main(string[] args)
    {
        MyList list = new MyList();
        for(int i=0; i<5; i++)  //Array Resized : 4, Array Resized : 5 가 출력된다
            list[i]=i; //배열을 다루듯 인덱스를 통해 데이터를 입력한다.
            
        for(int i=0;i<list.Length;i++) //데이터를 얻어올때도 인덱스를 이용한다.
            Console.WriteLine(list[i]); // 0 1 2 3 4 
    }
}

 

//????????

B. foreach가 가능한 객체 만들기

foreach문은 아무 형식의 객체에서는 사용할 수 없으며, 배열이나 리스트 같은 컬렉션에서만 사용할 수 있다. 위의 MyList클래스는 foreach문 사용이 불가능하다.

foreach문은 인터페이스 IEnumberable과 IEnumerator를 상속하는 형식만 지원하므로 MyList클래스에 두가지 인터페이스를 상속하고 인터페이스 내의 메소드를 구현하면 foreach문을 사용할 수 있게된다.

( ※ Enumerable : 열거가능한 , Enumerator : 열거자 )

메소드 설명
IEnumerator GetEnumerator() //IEnumerable의 유일한 메소드 IEnumerator 형식의 객체를 반환

 

GetEnumerator() 메소드 구현을 위해서는 현재 메소드(GetEnumerator())의 실행을 일시정지 시켜놓고 호출자에게 결과를 반환하는 yield return문을 사용해야한다. 

메소드가 다시 호출되면, 일시 정지된 실행을 복구하여 yield return 또는 yield  break문을 만날때까지 나머지작업을 실행한다.

// 이 코드에서 yield를 사용하는이유는 이해가 안되므로, 다시 확인필요
class MyEnumerator
{
    int[] numbers={1,2,3,4};
    public IEnumerator GetEnumerator()
    {
        yield return numbers[0];
        yield return numbers[1];
        yield return numbers[2];
        yield break; //yield break는 GetEnumerator() 메소드를 종료시킨다.
        yield return numbers[3]; //따라서, 이코드는 실행되지 않는다.
    }
}

class MainApp
{
    static void Main(String[] args)
    {
        var obj=new MyEnumerator();
        foreach (int i in obj)
            Console.WriteLine(i);
    }
}

 

또한, GetEnumerator() 메소드는 IEnumerator 형식의 객체, 즉 IEnumerator 인터페이스를 상속하는 클래스의 객체를 반환하면된다. 다음은 IEnumerator 인터페이스의 메소드 및 프로퍼티 목록이다.

메소드 또는 프로퍼티 설명
boolean MoveNext() 다음 요소로 이동한다. 컬렉션의 끝을 지난 경우에는 false, 이동이 성공한 경우에는 true를 반환한다.
void Reset() 컬렉션의 첫 번째 위치의 "앞"으로 이동한다. 첫 번째 위치가 0번이라면, Reset()을 호출하면 -1번으로 이동한다. 첫 번째 위치로의 이동은 MoveNext()를 호출한 다음에 이뤄진다.
Object Current {get;} 컬렉션의 현재 요소를 반환한다.
class MyList : IEnumerable, IEnumerator
{
    private int[] array;
    int position = -1; //컬렉션의 현재 위치를 다루는 변수이며 초기값은 0이아닌 -1이다. 0은 배열의 첫번째 요소를 
                       //가리키는 수. 
                       //position이 이 값(0)을 갖고있을때 foreach문이 첫 번째 반복을 수행하면 MoveNext() 
                       //메소드를 실행하고, 이때 position이 1이되어 두번째 요소를 가져오는 문제가 생긴다.

    public MyList()
    {
        array = new int[3];
    }
    public int this[int index]
    {
        get { return array[index]; }
        set
        {
            if (index >= array.Length)
            {
                Array.Resize<int>(ref array, index + 1);
                Console.WriteLine($"Array Resized : {array.Length}");
            }

            array[index] = value;
        }
    }

    //IEnumerable 멤버
    public object Current
    {
        get
        {
            return array[position];
        }
    }

    //IEnumerator 멤버
    public bool MoveNext()
    {
        if(position == array.Length - 1)
        {
            Reset();
            return false;
        }
        position++;
        return (position < array.Length);
    }

    //IEnumerator 멤버
    public void Reset()
    {
        position = -1;
    }

    //IEnumerable 멤버
    public IEnumerator GetEnumerator()
    {
        for(int i=0;i<array.Length;i++)
        {
            yield return (array[i]); //  yield를 쓰는이유에 대해 다시 확인필요
        }
    }
}
class MainApp
{
    static void Main(String[] args)
    {
        MyList list = new MyList();
        for (int i = 0; i < 5; i++)
            list[i] = i;

        foreach (int e in list)
            Console.WriteLine(e);
    }
}

 

메모 : yield의 사용법, 사용하는이유 재확인필요

http://www.csharpstudy.com/CSharp/CSharp-yield.aspx