ObservableCollection에 대해서

C#/Story|2019. 11. 2. 12:08

데이터 변경을 감지해서 처리해주는 로직에서 매 프레임마다 확인하는 건 비용이 비싸기 때문에
Observer 패턴을 사용해서 관심 있는 데이터가 해당 이벤트를 구독하는 방식을 많이 사용한다.
나는 ObservableData 클래스를 Generic 형식으로 작성해서 사용하고 있었는데
Collection에 Observer 패턴을 적용하고자 했으나 생각보다 고려해야 될 점이 많아 보류해두었다.
그래서 msdn을 확인하니 System.Collection.ObjectModel 네임스페이스에 ObservableCollection이 구현돼 있어 사용해보았다.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

public class Database {
    public ObservableCollection<int> list = new ObservableCollection<int>();
}
public class Test() {
    public static void Main() {
    	var db=new Database();
        db.list.CollectionChanged += (sender,args) => {
            if (args.Action==System.Collections.Specialized.NotifyCollectionChangedAction.Add) {
                //Added (args.NewItems[args.NewItems.Count - 1])
            }
            if(args.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove) {
               //Removed
            }
        }
    }
}

일단 검증된 Class 있다는건 반가웠지만 주로 Add,Remove에 대해서 Listening하기 위해서 사용될텐데

그 때문에 위와 같은 코드를 작성하는건 효율성도 떨어지고 직관적이지도 않은 것 같아

해당 클래스를 참조하여 간소화된 ObservableList와 ObservableDictionary를 작성할 예정이다.

'C# > Story' 카테고리의 다른 글

2차원 배열과 Matrix Class 제작  (0) 2019.09.23
yield 문과 Iterator에 대해서  (0) 2019.09.16

댓글()

2차원 배열과 Matrix Class 제작

C#/Story|2019. 9. 23. 16:46

프로그래밍을 하다보면 2차원에 관련된 데이터를 처리하는 경우가 많다.

보통 rows,columns 위치에 특정 데이터가 들어있는 구조인데 

C#에서는 int[,] 처럼 2차원 배열을 사용하거나 int[][] (List<List<int>>) 가변배열(가변 리스트)을 사용한다.

하지만 2차원 배열을 사용해보면 초기화를 하려면 이중 루프를 쓰거나 모든 요소를 적어주고

특정 조건의 값만 변경하려면 검사 로직과 변경 로직을 나눠서 써야한다던지 하는 불편함이 있다. 

예를 들어 x는 짝수이며 y는 2인 모든값을 특정값으로 변경하는 로직이 있다면 다음과 같이 작성해야한다.

public void Initialize() {
    int[,] data=new int[4,4];
    for (int i=0;i<data.GetLength(0);i++) {
    	for (int j=0;j<data.GetLength(1);j++) {
            if (i%2==0 && j==2) {
                data[i,j]=20;
            }
        }
    }
}

 

그래서 Matrix클래스를 작성해보도록 하였다. (여기서 Matrix는 수학에서의 Matrix와는 다르다)

내부 구조는 1차원 List를 생성하여 X,Y값을 고정시켜 Index접근을 Pair한 값(X,Y)만 접근 할 수 있게 했다.

(x,y,item)<Tuple> 이터레이터를 반환하는 함수를 만들어 Linq를 사용하기 용이하게 만들었고

다양한 생성자를 주어 기본적인 배열이나 List와도 호환이 가능하게 하고 검색과 변경을 편하게 만들었다. 

//일부 내용이 빠져있다. 
//실제 코드는 https://gitlab.com/tkdgns1284/bss_framework/blob/master/Runtime/Csharp/DataModel/Matrix.cs
public class Matrix<T> : IEnumerable<(int x,int y,T item)> {
    public Matrix(int x,int y) {
        Size = (x, y);
        for (int i=0;i<x*y;i++) {
            data.Add(default);
        }
    }
    public T this[int x,int y] {
        get => data[x*Size.y+y];
        set => data[x * Size.y + y] = value;
    }
    public T this[(int x, int y) pair] {
        get => data[pair.x * Size.y + pair.y];
        set => data[pair.x * Size.y + pair.y] = value;
    }
    public readonly (int x, int y) Size;
    private List<T> data = new List<T>();//내부구조는 1차원배열
    
    /// <summary>
    /// (x,y,item) 튜플의 이터레이터를 반환합니다.
    /// </summary>
    public IEnumerable<(int x,int y,T item)> GetEnumerable() {
        for (int i=0;i<Size.x;i++) {
            for (int j=0;j<Size.y;j++) {
                yield return (i,j,this[i, j]);
            }
        }
    }
    
    /// <summary>
    /// Predicate에 해당하는 요소를 item 값으로 변경합니다.
    /// </summary>
    public void Set(T item, Func<(int x, int y, T item), bool> predicate) {
        foreach(var it in GetEnumerable()) {
            if(!predicate(it))
                continue;
            this[it.x, it.y] = item;
        }
    }
}

 

Matrix클래스를 이용해서 같은 행동을 하는 함수를 정의해보았다.

public void Initialize() {
    Matrix<int> data=new Matrix<int>(4,4);
    //For문과 If문 같은 요소가 사라지고 간단해졌다.
    data.Set(20,t=>{ return t.x % 2 == 0 && t.y == 2; });
}

'C# > Story' 카테고리의 다른 글

ObservableCollection에 대해서  (0) 2019.11.02
yield 문과 Iterator에 대해서  (0) 2019.09.16

댓글()

yield 문과 Iterator에 대해서

C#/Story|2019. 9. 16. 17:29
IEnumrable<int> Range(int start,int end) {
    for (int i=start;i<end;i++) {
    	yield return i;
    }
}

이런식으로 yield return 같은 방법으로 값을 반환하는데 쓴다.

yield 키워드가 없었다면 이 함수는 항상 start(int)값을 반환하는 함수가 되었겠지만

yield 문은 함수가 어디까지 실행되었는지를 기억해서 그 이후부터 실행하기 때문에

이 함수는 start부터 end-1값까지의 이터레이터를 반환하는 함수가 된다.

그럼 이터레이터를 반환하지않고 그냥 리스트를 반환하면 되지라고 생각할 수도 있으나 

그 차이를 알아보기 위해 위 함수와 같은 행동을 하는 RangeInList 작성해보았다. 

IEnumrable<int> RangeInEnumerable(int start,int end) {
    for (int i=start;i<end;i++) {
    	Console.Write(i);
    	yield return i;
    }
}

List<int> RangeInList(int start,int end) {
    var list=new List<int>();
    for (int i=start;i<end;i++) {
    	Console.Write(i);
    	list.Add(i);
    }
    return list;
}

static void Main() {
    RangeInEnumerable(0,10000).Take(5).Foreach(x=>{
    	//Action
    });//Console Result: 01234
    
    RangeInList(0,10000).Take(5).Foreach(x=>{
    	//Action
    });//Console Result: 012345....9999 
}

이터레이터를 반환하는 함수는 해당 Element가 필요할 때 까지 지연했다가 쿼리가 끝날때 한번만 순회한다.

그러므로 Linq와 같은 쿼리구문과 궁합이 매우 좋고 시퀀스를 처리하는데 매우 적합하다.

'C# > Story' 카테고리의 다른 글

ObservableCollection에 대해서  (0) 2019.11.02
2차원 배열과 Matrix Class 제작  (0) 2019.09.23

댓글()