"깊이"가 다른 게임개발자 허민영

유저에서 게임까지, 철학에서 코딩까지, 본질을 보는 게임개발

소프트웨어 공학/코딩

C#] ReactiveProperty 및 ReactiveCollections 구현

허민영 2025. 3. 13. 21:09

Observer 패턴을 활용한 리액티브 프로퍼티 및 컬렉션은 데이터의 변화를 감지하고 이를 구독자에게 알리는 역할을 한다. 이를 통해 UI나 데이터 로직이 변화에 반응하도록 설계할 수 있으며, 특히 VIPER 패턴의 Interactor와 잘 어울리는 구조를 형성할 수 있다. 이번 글에서는 C#을 기반으로 구현한 ReactiveProperty<T>, ReactiveList<T>, ReactiveDictionary<K, V>, ReactiveArray<T> 클래스에 대해 설명한다.

1. 리액티브 프로퍼티

ReactiveProperty<T>는 단일 값의 변경을 감지하고 이를 구독자에게 전달하는 역할을 한다. 내부적으로 onChanged 델리게이트를 사용하여 값이 변경될 때만 이벤트를 발생시키도록 설계되었다.

public class ReactiveProperty<T> 
{
    private T value;
    private Action<T> onChanged;

    public ReactiveProperty(T initialValue, Action<T> onChanged = null)
    {
        this.value = initialValue;
        this.onChanged = onChanged;
    }

    public T Value
    {
        get => value;
        set
        {
            if (!Equals(this.value, value)) 
            {
                this.value = value;
                onChanged?.Invoke(value); 
            }
        }
    }

    public void AddListener(Action<T> callback)
    {
        onChanged += callback;
    }

    public void RemoveListener(Action<T> callback)
    {
        onChanged -= callback;
    }
}

위의 클래스는 값을 저장하고 Value 프로퍼티를 통해 접근할 수 있도록 한다. 값이 변경되었을 때만 onChanged 이벤트를 호출하여 불필요한 이벤트 호출을 방지한다.

2. 리액티브 리스트

ReactiveList<T>List<T>를 감싸는 형태로, 요소가 추가되거나 삭제될 때 OnChanged 이벤트를 호출하도록 설계되었다.

public class ReactiveList<T> : IList<T>
{
    private readonly List<T> _list = new List<T>();
    public Action OnChanged;

    public T this[int index]
    {
        get => _list[index];
        set
        {
            _list[index] = value;
            OnChanged?.Invoke();
        }
    }

    public void Add(T item)
    {
        _list.Add(item);
        OnChanged?.Invoke();
    }

    public bool Remove(T item)
    {
        bool removed = _list.Remove(item);
        if (removed) OnChanged?.Invoke();
        return removed;
    }
}

위 클래스에서는 List<T>의 기본적인 기능을 유지하면서도 요소가 추가, 삭제될 때 OnChanged 이벤트를 호출하여 변경 사항을 감지할 수 있도록 하였다.

3. 리액티브 딕셔너리

ReactiveDictionary<K, V>는 키-값 데이터를 저장하는 Dictionary<K, V>를 감싸는 구조로, 값이 변경될 때 OnChanged 이벤트를 호출하도록 구현되었다.

public class ReactiveDictionary<K, V> : IDictionary<K, V>
{
    private readonly Dictionary<K, V> _dictionary = new Dictionary<K, V>();
    public Action OnChanged;

    public V this[K key]
    {
        get => _dictionary[key];
        set
        {
            _dictionary[key] = value;
            OnChanged?.Invoke();
        }
    }

    public void Add(K key, V value)
    {
        _dictionary.Add(key, value);
        OnChanged?.Invoke();
    }

    public bool Remove(K key)
    {
        bool removed = _dictionary.Remove(key);
        if (removed) OnChanged?.Invoke();
        return removed;
    }
}

이 클래스는 기존 Dictionary<K, V>의 기능을 유지하면서 값이 변경될 때 이벤트를 발생시킨다. 이를 통해 데이터 변경을 실시간으로 감지하고 UI 등에 반영할 수 있다.

4. 리액티브 배열

배열은 크기가 고정되는 특성이 있으므로 Resize 기능을 추가하여 크기를 동적으로 조정할 수 있도록 하였다.

public class ReactiveArray<T> : IEnumerable<T>
{
    private T[] _array;
    public Action OnChanged;

    public int Length => _array.Length;

    public ReactiveArray(int size)
    {
        _array = new T[size];
    }

    public T this[int index]
    {
        get => _array[index];
        set
        {
            _array[index] = value;
            OnChanged?.Invoke();
        }
    }

    public void Resize(int newSize)
    {
        int oldSize = _array.Length;
        Array.Resize(ref _array, newSize);
        T defaultValue = default;

        for (int i = oldSize; i < newSize; i++)
        {
            _array[i] = defaultValue;
        }

        OnChanged?.Invoke();
    }
}

위 클래스는 배열의 요소 변경을 감지할 수 있도록 하며, Resize 메서드를 통해 동적으로 크기를 조절할 수 있도록 하였다


Observer 패턴을 활용한 ReactiveProperty 및 ReactiveCollections은 데이터의 변화를 감지하고 이를 실시간으로 반영하는 데 유용하게 사용할 수 있다. 특히, VIPER 패턴에서 Interactor와 잘 결합하여 데이터 상태 변화를 효과적으로 관리할 수 있다. 이번 구현에서는 List<T>, Dictionary<K, V>, Array 등을 래핑하여 이벤트 기반으로 동작하도록 만들었으며, 향후 성능 최적화 및 비동기 환경에서의 활용 가능성을 고려할 필요가 있다.