ImmutableStack
ImmutableStack는 .NET에서 제공하는불변 컬렉션Immutable Collections 중 하나로, 변경 불가능한 스택을 관리합니다. 불변 스택은 생성된 이후에 상태가 변경되지 않으며, 데이터의 추가, 제거가 필요할 때마다 새로운 스택을 생성합니다. 이러한 특성은 데이터 무결성을 보장하고, 멀티스레드 환경에서의 동시성 문제를 방지하는 데 큰 장점이 있습니다.
ImmutableStack의 특징
- 변경 불가능성: 스택이 생성된 이후에는 그 상태가 변경되지 않습니다. 데이터를 추가, 제거할 때마다 새로운 스택이 생성됩니다.
- 구조적 공유: 새로운 스택을 생성할 때 전체 데이터를 복사하지 않고, 변경된 부분만 복사하여 메모리 사용을 최적화합니다.
- 스레드 안전성: 불변 스택은 멀티스레드 환경에서 안전하게 사용할 수 있어, 동기화에 대한 고민을 덜어줍니다.
기본 사용법
using System;
using System.Collections.Immutable;
ImmutableStack<int> stack = ImmutableStack<int>.Empty;
stack = stack.Push(1);
stack = stack.Push(2);
stack = stack.Push(3);
Console.WriteLine(string.Join(", ", stack));
// 출력: 3, 2, 1
stack = stack.Pop(out int value);
Console.WriteLine(value); // 출력: 3
Console.WriteLine(string.Join(", ", stack));
// 출력: 2, 1
ImmutableStack<int>.Empty
를 사용하여 빈 스택을 생성합니다.stack.Push(1)
을 호출하면 새로운 스택에 요소가 추가됩니다.stack.Pop(out int value)
를 호출하여 스택의 맨 위 요소를 제거하고 반환합니다.- 기존 스택은 변경되지 않고 새로운 스택이 반환됩니다.
주요 메서드와 활용
생성 및 초기화
ImmutableStack은 직접 생성자를 사용하지 않고, 정적 메서드를 통해 빈 스택을 생성합니다.
ImmutableStack<int> emptyStack = ImmutableStack<int>.Empty;
ImmutableStack<int> stack = emptyStack.Push(1).Push(2).Push(3);
요소 추가 및 제거
- Push: 스택의 맨 위에 요소를 추가하여 새로운 스택을 반환합니다.
var newStack = stack.Push(4); // 기존 스택은 변경되지 않으며, 새로운 스택에만 4가 추가됩니다.
- Pop: 스택의 맨 위 요소를 제거하고 새로운 스택을 반환하며, 제거된 요소를 출력 변수로 반환합니다.
var updatedStack = stack.Pop(out int removedValue); // removedValue는 3이며, updatedStack은 나머지 요소를 포함합니다.
- Peek: 스택의 맨 위에 있는 요소를 제거하지 않고 반환합니다.
int top = stack.Peek(); // top은 3입니다.
- IsEmpty: 스택이 비어 있는지 여부를 확인합니다.
bool isEmpty = stack.IsEmpty; // isEmpty는 false입니다.
트랜잭션 복구 및 롤백 기능
ImmutableStack<T>
는 데이터가 추가될 때마다 새로운 스택 인스턴스를 생성하므로, 트랜잭션 복구나 롤백에 매우 유용합니다. 변경 사항을 스택에 기록하고, 문제가 발생했을 때 해당 트랜잭션을 취소하여 이전 상태로 되돌리는 기능을 구현할 수 있습니다.
- 트랜잭션 복구 및 롤백: 트랜잭션이나 작업 중 에러가 발생했을 때, 이전 상태로 안전하게 롤백할 수 있도록 불변 스택에 기록합니다.
- 변경 이력 관리: 각 작업 상태를 스택에 쌓아두고, 필요할 때 바로 이전 상태로 복원할 수 있습니다.
using System.Collections.Immutable;
public class TransactionManager<T>
{
private ImmutableStack<T> _transactionStack = ImmutableStack<T>.Empty;
public void BeginTransaction(T state)
{
_transactionStack = _transactionStack.Push(state); // 트랜잭션 상태 저장
}
public T Rollback()
{
if (_transactionStack.IsEmpty)
throw new InvalidOperationException("No transaction to rollback.");
_transactionStack = _transactionStack.Pop(out var previousState); // 이전 상태로 롤백
return previousState;
}
public T CurrentState => _transactionStack.Peek(); // 현재 상태 확인
}
// 사용 예제
var transactionManager = new TransactionManager<string>();
transactionManager.BeginTransaction("State 1");
transactionManager.BeginTransaction("State 2");
Console.WriteLine(transactionManager.CurrentState); // 출력: State 2
var rolledBackState = transactionManager.Rollback();
Console.WriteLine(rolledBackState); // 출력: State 1
무한히 깊은 되돌리기 및 재실행
ImmutableStack<T>
는 변경되지 않은 상태를 안전하게 참조할 수 있어, 이전 상태로 되돌리기Undo와 되돌린 상태에서 다시 실행Redo 기능을 구현할 때 유용합니다. Undo
스택과 Redo
스택을 분리하여 관리하면, 복잡한 상태 변경을 안정적으로 처리할 수 있습니다.
- 이전 상태로 복원:
Undo
스택에 변경 내역을 저장하고, 필요할 때 이전 상태로 복원합니다. - Redo 기능 구현:
Redo
스택에Undo
된 상태를 저장해 두고, 다시 실행할 수 있습니다.
using System;
using System.Collections.Immutable;
public class UndoRedoManager<T>
{
private ImmutableStack<T> _undoStack = ImmutableStack<T>.Empty;
private ImmutableStack<T> _redoStack = ImmutableStack<T>.Empty;
private T _currentState;
public UndoRedoManager(T initialState)
{
_currentState = initialState;
}
public void Do(T newState)
{
_undoStack = _undoStack.Push(_currentState); // 현재 상태를 Undo 스택에 저장
_currentState = newState;
_redoStack = ImmutableStack<T>.Empty; // 새로운 상태에서 Redo 스택 초기화
}
public T Undo()
{
if (_undoStack.IsEmpty)
throw new InvalidOperationException("No state to undo.");
_redoStack = _redoStack.Push(_currentState); // 현재 상태를 Redo 스택에 저장
_currentState = _undoStack.Peek();
_undoStack = _undoStack.Pop();
return _currentState;
}
public T Redo()
{
if (_redoStack.IsEmpty)
throw new InvalidOperationException("No state to redo.");
_undoStack = _undoStack.Push(_currentState); // 현재 상태를 다시 Undo 스택에 저장
_currentState = _redoStack.Peek();
_redoStack = _redoStack.Pop();
return _currentState;
}
public T CurrentState => _currentState;
}
// 사용 예제
var manager = new UndoRedoManager<string>("Initial State");
manager.Do("State 1");
manager.Do("State 2");
Console.WriteLine(manager.Undo()); // 출력: State 1
Console.WriteLine(manager.Redo()); // 출력: State 2