Enumeration
열거의 개념과 필요성
- 컴퓨팅에서는 다양한 종류의 컬렉션이 존재함
- 컬렉션 : 벡터, 리스트 등의 컨테이너다.
- 단순한 데이터 구조인 배열이나 연결 리스트
- 이러한 다양한 컬렉션들의 가장 기본적인 공통 요구사항은 내용을 순회하는 것임
- 복잡한 구조인 레드블랙 트리나 해시테이블
- 각 인터페이스는 계층화 되어있다.
- 확인 해야할 것들 : 인터페이스를 이용한 상속받은 클래스는 각 기능들을 사용자도 사용할 수 있게 된다.
- IEnumerable<T>는 ‘Enumeration only’ 즉, 순회하는것만 가능하다.
IEnumerable과 IEnumerator
IEnumerator 인터페이스
- 컬렉션의 요소를 순방향으로 열거하는 기본적인 저수준 프로토콜을 정의함
public interface IEnumerator
{
bool MoveNext(); // 다음 요소로 이동, 더 이상 요소가 없으면 false 반환
object Current { get; } // 현재 위치의 요소 반환
void Reset(); // 처음 위치로 되돌림
}
- MoveNext()
- 첫 요소를 얻기 전에 반드시 호출해야 함
- 빈 컬렉션의 경우를 처리하기 위한 설계임
- 다음 순회 요소가 첫 요소가 되고 true를 반환한다.
- Reset()
- 주로 COM 상호운용성을 위해 존재함
- COM : 상호 작용할 수 있는 이진 소프트웨어 구성 요소를 만들기 위한 플랫폼 독립적이고 분산된 개체 지향 시스템이다.
- 런타임에 상호 작용하는 재사용 가능한 소프트웨어 라이브러리를 만들기 위한 이진 상호 운용성 표준을 정의한다.
- 상호 운용성 : 하나의 시스템이 동일 또는 다른 시스템과 아무런 제약 없이 서로 호환되어 사용할 수 있는 성질이다.
- 보편적으로 지원되지 않아 직접 호출은 피해야 함
- 대신 새로운 열거자를 인스턴스화 하는 것이 좋음
IEnumerable 인터페이스
- 컬렉션은 보통 직접 열거자를 구현하지 않고, IEnumerable을 통해 열거자를 제공함
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
저수준 사용 예제
string s = "Hello";
// string이 IEnumerable을 구현하므로 GetEnumerator() 호출 가능
IEnumerator rator = s.GetEnumerator();
while (rator.MoveNext())
{
char c = (char)rator.Current;
Console.Write(c + ".");
}
// 출력: H.e.l.l.o.
- C#은 편의를 위해 foreach 문을 제공함
string s = "Hello";
foreach (char c in s)
Console.Write(c + ".");
// 출력: H.e.l.l.o.
비제네릭 인터페이스의 사용
비제네릭 인터페이스가 필요한 경우
- IEnumerable<T>를 구현할 때는 반드시 IEnumerable도 함께 구현해야 함
- 대부분의 경우 직접 구현보다는 반복기 메서드나 Collection<T>, LINQ를 사용함
컬렉션 타입 통합
- 비제네릭 인터페이스는 모든 요소 타입에 대해 컬렉션을 통합할 수 있음
- 예: 재귀적으로 모든 요소를 카운트하는 메서드
public static int Count(IEnumerable e)
{
int count = 0;
foreach (object element in e)
{
var subCollection = element as IEnumerable;
if (subCollection != null)
count += Count(subCollection);
else
count++; //순회가 불가능할 경우
}
return count;
}
public IEnumerator<int> GetEnumerator()
{
yield return 0;
}
IEnumerator IEnumerator GetEnumerator()
{
GetEnumerator<int>(); //이런 식으로 호출해라
}
- IEnumerable<object>를 사용하면 값 타입 요소나 IEnumerable<T>를 구현하지 않는 레거시 컬렉션(예: Windows Forms의 ControlCollection)에서 작동하지 않음
- IEnumerable은 Object 형으로 값을 반환하고, IEnumerable<T>는 특정 데이터 형으로 저장된다.
이터레이터 함수(Iterator Function)를 이용한 구현
- yield return을 포함하는 메서드, 속성, 인덱서를 이터레이터 함수라고 함
- 이터레이터 함수는 클래스를 작성하지 않고도 IEnumerable/IEnumerable<T>를 구현할 수 있게 해줌
이터레이터 함수의 특징
- 컴파일러가 yield return 문을 발견하면 숨겨진 중첩 클래스를 자동으로 생성함
- 이 숨겨진 클래스가 IEnumerator/IEnumerator<T>를 구현함
- GetEnumerator() 메서드는 이 숨겨진 클래스의 인스턴스를 반환하도록 리팩터링됨
이터레이터 함수의 장점
- 열거 로직을 간단하게 작성할 수 있음
- LINQ to Objects의 표준 쿼리 연산자 구현에도 사용됨
- 컴파일러가 자동으로 올바른 열거자 클래스를 생성해줌
- 원래는 인터페이스는 객체를 생성하지 못하지만 IEnumerator나 IEnumerable 같은 경우 yield return으로 객체 반환이 가능해진다.
// 메서드에서 이터레이터 함수 사용
public static IEnumerable<int> GetSomeIntegers()
{
yield return 1;
yield return 2;
yield return 3;
}
// 속성에서 이터레이터 함수 사용
public IEnumerable<int> Numbers
{
get
{
yield return 1;
yield return 2;
yield return 3;
//return new ~~ 원래 이렇게 써야한다.
}
}
// 인덱서에서 이터레이터 함수 사용
public IEnumerable<int> this[bool evenNumbers]
{
get
{
if (evenNumbers)
{
yield return 2;
yield return 4;
yield return 6;
}
else
{
yield return 1;
yield return 3;
yield return 5;
}
}
}
// 사용 예:
foreach (int i in GetSomeIntegers())
Console.WriteLine(i);
foreach (int i in obj.Numbers)
Console.WriteLine(i);
foreach (int i in obj[true]) // 짝수만 열거
Console.WriteLine(i);
제네릭 열거 인터페이스
IEnumerator<T>와 IEnumerable<T>의 특징
public interface IEnumerator<T> : IEnumerator, IDisposable
{
T Current { get; }
}
public interface IEnumerable<T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
- 반복자 함수로 yield return을 대체할 수 있다.
- IDisposable : 리소스 정리가 필요한 객체는 이것을 상속하면 해당 함수를 사용할 수 있게 된다.
- 장점:
- Current와 GetEnumerator의 타입 안전성 강화
- 값 타입 요소의 박싱 오버헤드 방지
- 소비자 입장에서 더 편리함
배열과 제네릭 열거
- 배열은 자동으로 IEnumerable<T> 구현함
int[] data = { 1, 2, 3 };
// 명시적 인터페이스를 통해 제네릭 IEnumerator<T> 얻기
var rator = ((IEnumerable<int>)data).GetEnumerator();
// foreach를 사용하면 위 과정이 자동으로 처리됨
foreach (int x in data)
Console.Write(x);
IEnumerable<T>와 IDisposable
- IEnumerator<T>는 IDisposable을 상속함
- 데이터베이스 연결 등 리소스를 보유한 열거자가 리소스를 해제할 수 있게 함
- foreach 문은 이를 인식하여 다음과 같이 변환됨:
// foreach (var element in somethingEnumerable) { ... }
// 위 코드는 아래처럼 변환됨:
using (var rator = somethingEnumerable.GetEnumerator())
while (rator.MoveNext())
{
var element = rator.Current;
// ...
}
열거 인터페이스 구현
구현이 필요한 경우
- foreach 문 지원
- 표준 컬렉션과의 상호운용
- 더 복잡한 컬렉션 인터페이스 요구사항 충족
- 컬렉션 초기화자 지원
- LINQ를 사용하면 IEnumerable을 상속한 클래스는 LINQ에넣어 사용할 수 있게 된다.
구현 방법
- 다른 컬렉션의 열거자 반환
- yield return 사용
- IEnumerator/IEnumerator<T> 직접 구현
1. 다른 컬렉션의 열거자 반환
- 가장 간단한 방법이지만 내부 컬렉션의 항목이 정확히 원하는 것일 때만 가능함
// 사용 예:
var collection = new StringCollection();
collection.Add("첫 번째");
collection.Add("두 번째");
collection.Add("세 번째");
foreach (string item in collection) // 내부 List<string>의 열거자를 사용하여 열거
Console.WriteLine(item);
// 문자열 리스트를 감싸는 단순한 래퍼 클래스
public class StringCollection : IEnumerable<string>
{
// 내부 컬렉션
private List<string> items = new List<string>();
// 내부 컬렉션에 항목 추가
public void Add(string item)
{
items.Add(item);
}
// 내부 컬렉션의 열거자를 반환
public IEnumerator<string> GetEnumerator()
{
return items.GetEnumerator(); // List<string>의 열거자를 그대로 반환
}
// 비제네릭 버전도 구현
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
2. yield return 사용 -
public class MyCollection : IEnumerable
{
int[] data = { 1, 2, 3 };
public IEnumerator GetEnumerator()
{
foreach (int i in data)
yield return i;
}
}
public class MyGenCollection : IEnumerable<int>
{
int[] data = { 1, 2, 3 };
public IEnumerator<int> GetEnumerator()
{
foreach (int i in data)
yield return i;
}
// 명시적 구현으로 숨김
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
3. IEnumerator 직접 구현
public class MyIntList : IEnumerable
{
int[] data = { 1, 2, 3 };
public IEnumerator GetEnumerator() => new Enumerator(this);
class Enumerator : IEnumerator
{
MyIntList collection;
int currentIndex = -1;
public Enumerator(MyIntList items) => this.collection = items;
public object Current
{
get
{
if (currentIndex == -1)
throw new InvalidOperationException("Enumeration not started!");
if (currentIndex == collection.data.Length)
throw new InvalidOperationException("Past end of list!");
return collection.data[currentIndex];
}
}
public bool MoveNext()
{
if (currentIndex >= collection.data.Length - 1) return false;
return ++currentIndex < collection.data.Length;
}
public void Reset() => currentIndex = -1;
}
}
class MyIntList : IEnumerable<int>
{
int[] data = { 1, 2, 3 };
public IEnumerator<int> GetEnumerator() => new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); //위의 호출을 이렇게도 사용할 수 있다.
class Enumerator : IEnumerator<int>
{
int currentIndex = -1;
MyIntList collection;
public Enumerator(MyIntList items) => this.collection = items;
public int Current => collection.data[currentIndex];
object IEnumerator.Current => Current;
public bool MoveNext() => ++currentIndex < collection.data.Length;
public void Reset() => currentIndex = -1;
void IDisposable.Dispose() { }
}
}
구현 시 주의사항
- Reset 메서드는 선택적으로 구현 가능함
- 지원하지 않을 경우 NotSupportedException을 던지면 됨
- MoveNext의 첫 호출은 첫 번째 항목으로 이동해야 함
- 두 번째 항목이 아닌 첫 번째 항목으로 이동해야 함
성능 고려사항
- 제네릭 버전이 값 타입에서 박싱/언박싱이 없어 더 빠름
- yield return을 사용한 반복기는 컴파일러가 상태 기계를 생성하므로 편리함
- 직접 구현은 더 복잡하지만 특별한 최적화가 필요한 경우에 유용함
ICollection과 IList Interface
- .NET은 열거(Enumeration) 기능을 제공하는 인터페이스 외에도 컬렉션의 크기를 확인하고, 인덱스로 멤버에 접근하며, 검색하고, 수정할 수 있는 기능을 제공하는 인터페이스들을 정의함
- 이러한 기능은 ICollection, IList, IDictionary 인터페이스를 통해 제공됨
- 각각의 인터페이스는 제네릭과 비제네릭 버전이 있음. 단, 비제네릭 버전은 주로 이전 버전과의 호환성을 위해 존재함
인터페이스 계층 구조
- 각 인터페이스는 다음과 같은 기능을 제공함:
- IEnumerable<T> (및 IEnumerable): 최소 기능(열거만 가능)
- ICollection<T> (및 ICollection): 중간 기능(Count 속성 등)
- IList<T>/IDictionary<K,V> (및 비제네릭 버전): 최대 기능(인덱스/키로 임의 접근 가능)
- 대부분의 경우 이러한 인터페이스를 직접 구현할 필요는 없으며, Collection<T>를 상속하여 컬렉션 클래스를 작성하는 것이 더 효율적임
ICollection<T>와 ICollection 인터페이스
ICollection<T> 인터페이스
- ICollection<T>는 개수를 셀 수 있는 객체의 컬렉션을 위한 표준 인터페이스임
- 다음과 같은 기능을 제공함:
- 컬렉션의 크기 확인(Count)
- 아이템 존재 여부 확인(Contains)
- 배열로 복사(ToArray)
- 읽기 전용 여부 확인(IsReadOnly)
- 쓰기 가능한 컬렉션의 경우 아이템 추가(Add), 제거(Remove), 모두 제거(Clear) 가능
- IEnumerable<T>를 상속하므로 foreach 문으로 순회 가능함
- IEnumerable<T>를 상속하려면 IEnumerable(비제네릭 버전)도 같이 상속받아야한다.
public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
int Count { get; }
bool Contains(T item); //탐색 함수
void CopyTo(T[] array, int arrayIndex); //복사 함수
bool IsReadOnly { get; }
void Add(T item);
bool Remove(T item);
void Clear();
}
ICollection 인터페이스
- 비제네릭 버전은 아이템의 추가/제거 기능이 없음
- 대신 스레드 동기화를 위한 속성들을 제공함(제네릭 버전에서는 제외됨)
public interface ICollection : IEnumerable
{
int Count { get; }
bool IsSynchronized { get; }
object SyncRoot { get; }
void CopyTo(Array array, int index);
}
IList<T>와 IList 인터페이스
IList<T> 인터페이스
- IList<T>는 위치로 인덱싱할 수 있는 컬렉션을 위한 표준 인터페이스임
- ICollection<T>와 IEnumerable<T>에서 상속받은 기능 외에도 다음을 제공함:
- 위치로 요소 읽기/쓰기(인덱서 사용)
- 위치로 삽입/제거
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
T this[int index] { get; set; } //인덱서가 추가된것이다.
int IndexOf(T item);
void Insert(int index, T item);
void RemoveAt(int index);
}
- IndexOf 메서드는 배열에서 선형 검색을 수행하며, 지정된 아이템이 없으면 -1을 반환함
IList 인터페이스
- 비제네릭 버전은 ICollection에서 적은 기능을 상속받기 때문에 더 많은 멤버를 가짐
public interface IList : ICollection, IEnumerable
{
object this[int index] { get; set }
bool IsFixedSize { get; }
bool IsReadOnly { get; }
int Add(object value);
void Clear();
bool Contains(object value);
int IndexOf(object value);
void Insert(int index, object value);
void Remove(object value);
void RemoveAt(int index);
}
- 비제네릭 IList의 Add 메서드는 새로 추가된 아이템의 인덱스를 반환함. 반면 ICollection<T>의 Add 메서드는 void 반환형을 가짐
다차원 배열과 IList 사용 시 주의사항
- 다차원 배열을 IList의 인덱서로 접근하려고 하면 ArgumentException이 발생함
- 이는 겉보기에는 안전해 보이는 다음과 같은 메서드에서도 문제가 될 수 있음:
public object FirstOrNull(IList list)
{
if (list == null || list.Count == 0) return null;
return list[0];
}
- 다차원 배열을 런타임에 확인하려면 다음 표현식을 사용함:
list.GetType().IsArray && list.GetType().GetArrayRank() > 1
제네릭과 비제네릭 인터페이스 구현
- IList<T>가 IList를 상속하지 않는 것은 타입 안전성을 위한 것임
- 만약 상속했다면, IList<T>로 캐스팅할 때 Add(T)와 Add(object) 멤버를 모두 갖게 되어 정적 타입 안전성이 깨질 수 있음
- 이러면 어떤 타입의 객체든 Add로 추가할 수 있게 됨
읽기 전용 인터페이스
IReadOnlyCollection<T>와 IReadOnlyList<T>
- .NET은 읽기 전용 작업에 필요한 멤버만 노출하는 컬렉션과 리스트 인터페이스도 정의함
public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
{
int Count { get; }
}
public interface IReadOnlyList<out T> : IReadOnlyCollection<T>,
IEnumerable<T>, IEnumerable
{
T this[int index] { get; }
}
- 이러한 인터페이스들의 타입 파라미터는 출력 위치에서만 사용되므로 공변성(covariant)으로 표시됨
- 예: 고양이 리스트를 동물의 읽기 전용 리스트로 취급할 수 있음
- 읽기 전용 인터페이스들은 컬렉션이나 리스트의 읽기 전용 뷰를 나타내며, 기본 구현은 여전히 쓰기 가능할 수 있음
- 대부분의 쓰기 가능한(가변) 컬렉션들은 읽기 전용과 읽기/쓰기 인터페이스를 모두 구현함
- IReadOnlyList<T>는 Windows Runtime 타입 IVectorView<T>에 매핑됨
- 읽기 전용 인터페이스를 사용하면 클래스가 공개적으로는 읽기 전용 뷰를 노출하면서 내부적으로는 쓰기 가능한 private 컬렉션을 유지할 수 있음
- 이는 클래스의 캡슐화(encapsulation)와 불변성(immutability)을 향상시킴
읽기 전용 인터페이스 사용 예제
public class Team
{
// 내부적으로는 수정 가능한 리스트를 사용
private List<string> members = new List<string>();
// 외부에는 읽기 전용 인터페이스로 노출
public IReadOnlyList<string> Members => members; //읽기전용으로 형변환
// 멤버 추가는 오직 Team 클래스의 메서드를 통해서만 가능
public void AddMember(string name)
{
members.Add(name);
}
}
// 사용 예시
class Program
{
static void Main()
{
Team team = new Team();
team.AddMember("Alice");
team.AddMember("Bob");
// 읽기는 가능
foreach (string member in team.Members)
{
Console.WriteLine(member);
}
// 컴파일 에러 - Members는 읽기 전용
// team.Members.Add("Charlie"); // 이 줄은 컴파일되지 않음
// 인덱서로 읽기는 가능
Console.WriteLine(team.Members[0]); // "Alice" 출력
// 컴파일 에러 - 수정 불가능
// team.Members[0] = "Carol"; // 이 줄은 컴파일되지 않음
}
}
- 내부적으로 List<string>을 사용하여 멤버를 관리함
- 외부에는 IReadOnlyList<string>을 통해 읽기 전용 접근만 허용함
- 멤버 추가는 오직 AddMember 메서드를 통해서만 가능함
- 이를 통해 팀 멤버 리스트의 무결성(integrity)을 보장할 수 있음
배열 클래스(Array Class)
기본 개념
- 배열 클래스(Array class)는 모든 단일 차원과 다차원 배열의 암시적 기본 클래스임.
- 배열은 IList<T>와 IList 인터페이스를 제네릭과 비제네릭 형태 모두 구현함.
- IList를 상속하고 있으니 IEnumerable, IEnumerator도 상속하고 있다.
- IList<T> 구현은 명시적으로 되어있어 Add나 Remove 같은 크기를 변경하는 메서드는 호출 시 예외를 발생시킴.
- Array는 실제로 정적 메서드를 많이 제공하므로, 인스턴스 메서드가 있을 것으로 예상되는 부분에서도 정적 메서드를 찾아봐야 함.
- 기본 자료형에 관계없이 모든 배열에 공통된 메서드를 제공함.
- Array 클래스는 배열의 형식 통합(type unification)을 제공하여 차원이나 요소 형식에 관계없이 일관된 작업이 가능함.
배열의 특징
- C#은 배열 선언과 초기화를 위한 특별한 구문을 제공함.
- CLR은 배열 선언 시 암시적으로 Array 클래스를 상속받는 의사 형식(pseudo-type)을 생성함.
- 생성된 의사 형식은 제네릭 컬렉션 인터페이스(IList<string> 등)를 구현함.
- 배열은 메모리에 연속적으로 할당되어 인덱싱이 매우 효율적임.
- 크기가 고정되어 있어 나중에 크기를 변경할 수 없음.
배열의 구조
- CLR은 배열에 대해 2GB 크기 제한을 둠(32비트나 64비트 환경 모두 해당).
- Array 클래스는 64비트 인덱서를 제한적으로 지원하지만, 2GB 제한으로 인해 실제로는 활용이 제한됨.
- 컬렉션이 고민되는 경우라면 일단 ‘배열’로 구현하는게 좋다.
값 형식과 참조 형식 요소
// 값 형식 배열: 각 요소가 실제 값을 저장
long[] numbers = new long[3]; // 각 요소 8바이트, 총 24바이트
numbers[0] = 12345;
numbers[1] = 54321;
// 참조 형식 배열: 각 요소가 참조를 저장
StringBuilder[] builders = new StringBuilder[5]; // 64비트에서 각 요소 8바이트
builders[0] = new StringBuilder("builder1");
builders[1] = new StringBuilder("builder2");
builders[2] = new StringBuilder("builder3");
- 값형식의 경우가 연속적인 공간 할당의 장점이 적용되는 예시다.
배열 참조 특성
- 배열은 요소 형식에 관계없이 항상 참조 형식임.
- 동일한 배열을 참조하는 두 변수는 같은 배열을 가리킴.
- 별도의 배열은 구조적으로 동일해도 등가 비교시 false를 반환함.
object[] a1 = { "string", 123, true };
object[] a2 = { "string", 123, true };
Console.WriteLine(a1 == a2); // False
Console.WriteLine(a1.Equals(a2)); // False
// 구조적 비교시에는 true -> 값 비교라는 것이다.
IStructuralEquatable se1 = a1;
Console.WriteLine(se1.Equals(a2, StructuralComparisons.StructuralEqualityComparer)); // True
- StructuralEqualityComparer : 구조적 동등성 비교 옵션이다.
- 값 형식 비교 라는 뜻이다.
- 이렇게 하면 굳이 형변환해서 값을 비교하지 않아도 된다.
배열 복제
- Clone() 메서드로 배열을 복제할 수 있음.
- 얕은 복사(shallow copy)가 수행됨:
- 값 형식: 값이 복사됨
- 참조 형식: 참조만 복사됨
StringBuilder[] builders = new StringBuilder[5];
builders[0] = new StringBuilder("one");
builders[1] = new StringBuilder("two");
builders[2] = new StringBuilder("two");
StringBuilder[] builders2 = builders;
// 얕은 복사 수행
StringBuilder[] shallowClone = (StringBuilder[]) builders.Clone();
// 원본과 복제본이 같은 객체를 참조
builders[0].Append("!");
Console.WriteLine(shallowClone[0]); // "one!" 출력
배열 생성과 인덱싱
기본 문법
// 배열 생성과 초기화
int[] myArray = { 1, 2, 3 };
// 인덱스로 접근
int first = myArray[0];
int last = myArray[myArray.Length - 1];
동적 배열 생성
- Array.CreateInstance() 메서드로 런타임에 배열 생성 가능함
- 요소 형식, 차원, 하한 경계를 지정할 수 있음
- 이런게 있다는것만 알아둬라
// 기본 사용법 - 2개 요소를 가진 문자열 배열 생성
Array a = Array.CreateInstance(typeof(string), 2);
a.SetValue("hi", 0); // a[0] = "hi" 와 동일
a.SetValue("there", 1); // a[1] = "there" 와 동일
string s = (string)a.GetValue(0); // s = a[0] 와 동일
// C# 배열로 캐스팅 가능
string[] cSharpArray = (string[])a;
string s2 = cSharpArray[0];
// 하한 경계가 1인 배열 생성
int[] lengths = { 3 }; // 길이 지정
int[] lowerBounds = { 1 }; // 하한 경계 지정
Array oneBasedArray = Array.CreateInstance(typeof(int), lengths, lowerBounds);
oneBasedArray.SetValue(100, 1); // 인덱스 1에 값 설정
oneBasedArray.SetValue(200, 2); // 인덱스 2에 값 설정
oneBasedArray.SetValue(300, 3); // 인덱스 3에 값 설정
Console.WriteLine(oneBasedArray.GetValue(1)); // 100
Console.WriteLine(oneBasedArray.GetLowerBound(0)); // 1
Console.WriteLine(oneBasedArray.GetUpperBound(0)); // 3
배열 요소 접근과 열거
GetValue와 SetValue
- 모든 배열에서 사용 가능한 통합된 메서드
- 다차원 배열의 경우 인덱스 배열을 매개변수로 사용
public object GetValue(params int[] indices)
public void SetValue(object value, params int[] indices)
// 사용 예시
void WriteFirstValue(Array a)
{
Console.Write(a.Rank + "-dimensional; ");
int[] indexers = new int[a.Rank];
Console.WriteLine("First value is " + a.GetValue(indexers));
}
void Demo()
{
int[] oneD = { 1, 2, 3 };
int[,] twoD = { {5,6}, {8,9} };
WriteFirstValue(oneD); // "1-dimensional; First value is 1" 출력
WriteFirstValue(twoD); // "2-dimensional; First value is 5" 출력
}
배열 열거
- foreach 문으로 쉽게 열거 가능
- Array.ForEach 정적 메서드 사용 가능
int[] myArray = { 1, 2, 3 };
// foreach 사용
foreach (int val in myArray)
Console.WriteLine(val);
// Array.ForEach 사용
Array.ForEach(new[] { 1, 2, 3 }, Console.WriteLine);
- ForEach<int>가 작동되는 것이다.
- 이유 : new[]의 요소가 int형이기 때문이다.
길이와 차원
관련 메서드와 속성
public int GetLength(int dimension); // 지정 차원의 길이
public long GetLongLength(int dimension); // 64비트 버전
public int Length { get; } // 총 요소 수
public long LongLength { get; } // 64비트 버전
public int GetLowerBound(int dimension); // 하한 경계
public int GetUpperBound(int dimension); // 상한 경계
public int Rank { get; } // 차원 수
사용 예제
// 2차원 배열 생성
int[,] matrix = new int[3, 4];
// 차원과 길이 정보 출력
Console.WriteLine($"차원 수: {matrix.Rank}"); // 2
Console.WriteLine($"총 요소 수: {matrix.Length}"); // 12
Console.WriteLine($"첫 번째 차원 길이: {matrix.GetLength(0)}"); // 3
Console.WriteLine($"두 번째 차원 길이: {matrix.GetLength(1)}"); // 4
// 경계 정보 출력
Console.WriteLine($"첫 차원 하한: {matrix.GetLowerBound(0)}"); // 0
Console.WriteLine($"첫 차원 상한: {matrix.GetUpperBound(0)}"); // 2
Console.WriteLine($"둘째 차원 하한: {matrix.GetLowerBound(1)}"); // 0
Console.WriteLine($"둘째 차원 상한: {matrix.GetUpperBound(1)}"); // 3
- GetLowerBound(차원) : 해당 차원의 최솟값
- GetUpperBound(차원) : 해당 차원의 최댓값