ImmutableArray
ImmutableArray<T>
는 .NET에서 제공하는 불변 컬렉션Immutable Collections 중 하나로, 변경 불가능한 배열을 관리합니다. 불변 배열은 생성된 이후에 상태가 변경되지 않으며, 데이터의 추가, 삭제, 수정이 필요할 때마다 새로운 배열을 생성합니다. 이러한 특성은 데이터 무결성을 보장하고, 멀티스레드 환경에서의 동시성 문제를 방지하는 데 큰 장점이 있습니다.
불변 배열의 특징
- 고성능 데이터 접근: 배열은 인덱스를 통한 빠른 데이터 접근이 가능하며, 불변 배열은 이러한 이점을 유지하면서 불변성을 제공합니다.
- 구조적 공유를 미지원:
ImmutableArray<T>
는 실제로 구조적 공유를 지원하지 않으며, 데이터 변경 시 전체 배열이 복사됩니다. 배열은 연속된 메모리 공간을 사용해야 하므로, 기존 메모리 블록에 요소를 추가하거나 삭제하는 등의 동적 변경이 불가능합니다.
기본 사용법
using System;
using System.Collections.Immutable;
class Program
{
static void Main()
{
ImmutableArray<int> array = ImmutableArray.Create(1, 2, 3);
Console.WriteLine(string.Join(", ", array));
// 출력: 1, 2, 3
ImmutableArray<int> newArray = array.Add(4);
Console.WriteLine(string.Join(", ", newArray));
// 출력: 1, 2, 3, 4
// 기존 배열과 새로운 배열이 다른지 확인
Console.WriteLine(object.ReferenceEquals(array, newArray));
// 출력: False
}
}
ImmutableArray.Create(1, 2, 3)
을 사용하여 배열을 생성합니다.array.Add(4)
를 호출하면 새로운 배열newArray
가 생성됩니다.array
는 변경되지 않고 그대로 유지됩니다.
주요 메서드와 활용
생성 및 초기화
// 빈 배열 생성
ImmutableArray<int> emptyArray = ImmutableArray<int>.Empty;
// 요소를 추가하여 배열 생성
ImmutableArray<int> numbers = ImmutableArray.Create(1, 2, 3);
// 기존 배열로부터 생성
int[] originalArray = { 4, 5, 6 };
ImmutableArray<int> immutableFromArray = ImmutableArray.Create(originalArray);
요소 추가 및 제거
- Add: 배열에 요소를 추가하여 새로운 배열을 반환합니다.
var newArray = numbers.Add(4);
- AddRange: 여러 요소를 한꺼번에 추가합니다.
var newArray = numbers.AddRange(new[] { 5, 6, 7 });
- Remove: 특정 요소를 제거하여 새로운 배열을 반환합니다.
var updatedArray = numbers.Remove(2);
- RemoveRange: 여러 요소를 제거합니다.
var updatedArray = numbers.RemoveRange(new[] { 1, 3 });
- Insert: 특정 위치에 요소를 삽입하여 새로운 배열을 반환합니다.
var newArray = numbers.Insert(1, 10);
- InsertRange: 특정 위치에 여러 요소를 삽입합니다.
var newArray = numbers.InsertRange(1, new[] { 10, 11 });
- Contains: 배열에 특정 요소가 포함되어 있는지 여부를 확인합니다.
bool contains = numbers.Contains(2);
- IndexOf: 특정 요소의 인덱스를 반환합니다.
int index = numbers.IndexOf(2);
- Clear: 모든 요소를 제거하고 빈 배열을 반환합니다.
var clearedArray = numbers.Clear();
- ToBuilder: 배열을 변경 가능한
ImmutableArray.Builder
로 변환하여 여러 번의 변경 작업을 효율적으로 수행한 후, 다시 불변 배열로 변환합니다.var builder = numbers.ToBuilder(); builder.Add(4); builder.Add(5); var updatedArray = builder.ToImmutable();
ImmutableList 와의 차이
ImmutableArray<T>
는 배열의 구조적 특성상 구조적 공유를 지원하지 않으며, 요소 추가나 수정 시 새로운 배열을 할당하고 전체 데이터를 복사하는 방식으로 불변성을 유지합니다.ImmutableList<T>
는 내부적으로 트리 기반 구조를 사용하여 구조적 공유를 지원하며, 변경 부분만 복사하고 나머지 요소는 재사용할 수 있어 메모리 효율이 뛰어납니다. 따라서ImmutableArray<T>
는 데이터 변경이 거의 없는 고정된 읽기 전용 데이터에 적합하고,ImmutableList<T>
는 데이터 추가/삭제 작업이 필요한 경우에 효율적인 불변 컬렉션입니다.
고급 활용과 최적화
ImmutableArray<T>
는 불변성과 고정 크기 배열의 특성을 가지고 있어, 고정된 데이터를 안전하게 참조해야 하거나 성능을 최적화해야 하는 시나리오에서 유용합니다. 특히 읽기 전용의 안정성, 스레드 안전성, 고성능 데이터 참조가 필요한 경우 활용할 수 있습니다. 아래는 ImmutableArray<T>
의 고급 활용 주제입니다.
고정된 데이터의 캐싱 및 스냅샷 관리
ImmutableArray<T>
는 불변성이 보장된 데이터 캐싱에 적합합니다. 초기화 시 한 번 데이터를 설정하고 이후에 변경되지 않는 캐시 데이터나 스냅샷 데이터를 안전하게 저장하고 참조할 수 있습니다. 이를 통해 여러 스레드에서 데이터를 읽어도 안전하며, 추가적인 동기화 비용 없이 캐시 데이터를 공유할 수 있습니다.
- 불변 데이터 캐싱: 자주 참조되지만 변경되지 않는 데이터를
ImmutableArray<T>
에 캐싱하여 성능을 최적화합니다. - 스냅샷 관리: 데이터베이스 조회 결과나 연산 결과를 스냅샷으로 저장해, 이후 참조를 빠르게 할 수 있도록 합니다.
using System;
using System.Collections.Immutable;
public class SnapshotCache<T>
{
private ImmutableArray<T> _cache;
public SnapshotCache(T[] initialData)
{
_cache = ImmutableArray.Create(initialData); // 초기 데이터를 불변 배열로 캐싱
}
public ImmutableArray<T> GetSnapshot()
{
return _cache; // 안전하게 불변 데이터를 반환
}
}
// 사용 예제
var initialData = new int[] { 1, 2, 3, 4, 5 };
var cache = new SnapshotCache<int>(initialData);
var snapshot = cache.GetSnapshot();
foreach (var item in snapshot)
{
Console.WriteLine(item);
}
// 출력:
// 1
// 2
// 3
// 4
// 5
- 초기 데이터를
ImmutableArray<T>
로 변환해 캐싱합니다. - 여러 스레드에서 안전하게 데이터를 참조할 수 있으며, 변경되지 않는 데이터에 대해 캐싱 비용을 최소화할 수 있습니다.
멀티스레드 환경에서 고정 데이터의 안전한 참조
멀티스레드 환경에서는 동기화 없이 데이터를 안전하게 참조해야 하는 경우가 많습니다. ImmutableArray<T>
는 불변성이 보장되기 때문에 여러 스레드에서 동기화 없이 안전하게 데이터를 참조할 수 있어, 성능이 중요한 멀티스레드 데이터 조회에 적합합니다.
- 멀티스레드 데이터 참조: 여러 스레드가 동일한 데이터를 읽어야 하는 상황에서, 동기화 비용 없이 안전하게 데이터를 공유.
- 동시성 문제 해결:
ImmutableArray<T>
는 데이터의 일관성을 유지하므로 동시성 문제를 최소화합니다.
using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
public class ThreadSafeDataAccess<T>
{
private ImmutableArray<T> _dataArray;
public ThreadSafeDataAccess(T[] data)
{
_dataArray = ImmutableArray.Create(data); // 고정된 데이터를 불변 배열로 설정
}
public T GetDataAt(int index)
{
return _dataArray[index]; // 동기화 없이 안전하게 데이터 접근
}
}
// 사용 예제
var data = new int[] { 10, 20, 30, 40, 50 };
var safeAccess = new ThreadSafeDataAccess<int>(data);
Parallel.For(0, 5, i =>
{
Console.WriteLine(safeAccess.GetDataAt(i));
});
// 출력 예:
// 10
// 20
// 30
// 40
// 50 (순서는 다를 수 있음)
ImmutableArray<T>
를 사용하여 멀티스레드 환경에서 데이터를 안전하게 참조할 수 있습니다.- 데이터가 변경되지 않으므로 동기화 비용 없이 고정된 데이터를 빠르게 접근할 수 있습니다.
불변 배열을 통한 데이터 변경 감지
ImmutableArray<T>
는 변경할 수 없으므로, 데이터의 무결성 검사를 수행하거나 변경 여부를 감지하는 데 유용합니다. ImmutableArray<T>
를 사용해 데이터가 변경되지 않았음을 보장하거나, 필요한 경우 새로운 배열을 생성하여 변경 사항을 추적할 수 있습니다.
- 데이터 무결성 검사: 불변 배열로 데이터를 저장하여, 데이터가 변경되지 않았음을 보장.
- 변경 감지 및 추적: 새로운 배열을 생성하여 변경 사항을 추적하고, 필요할 때만 데이터가 업데이트되도록 관리.
using System;
using System.Collections.Immutable;
public class DataIntegrityChecker<T>
{
private ImmutableArray<T> _originalData;
public DataIntegrityChecker(T[] data)
{
_originalData = ImmutableArray.Create(data); // 초기 데이터를 불변 배열로 설정
}
public bool HasDataChanged(ImmutableArray<T> newData)
{
return !_originalData.SequenceEqual(newData); // 변경 여부 확인
}
}
// 사용 예제
var initialData = new int[] { 1, 2, 3 };
var checker = new DataIntegrityChecker<int>(initialData);
var newData = ImmutableArray.Create(1, 2, 4);
Console.WriteLine(checker.HasDataChanged(newData)); // 출력: True
ImmutableArray<T>
를 사용해 데이터의 불변성을 보장하고, 새로운 데이터와 비교하여 변경 여부를 검사합니다.- 이를 통해 데이터 무결성을 관리할 수 있습니다.