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