성능 최적화: 데이터 바인딩

WPF(Windows Presentation Foundation) 데이터 바인딩은 애플리케이션에서 데이터를 제공하고 조작할 수 있는 간단하고 일관된 방법을 제공합니다. 다양한 데이터 원본에서 CLR 개체 및 XML의 형식으로 데이터에 요소를 바인딩할 수 있습니다.

이 항목에서는 데이터 바인딩과 관련된 성능 권장 사항을 제공합니다.

데이터 바인딩 참조를 확인하는 방법

데이터 바인딩 성능 문제를 다루기 전에 WPF(Windows Presentation Foundation) 데이터 바인딩 엔진에서 바인딩의 개체 참조를 확인하는 방법을 살펴보는 것이 좋습니다.

WPF(Windows Presentation Foundation) 데이터 바인딩의 원본은 CLR 개체일 수 있습니다. CLR 개체의 속성, 하위 속성 또는 인덱서에 바인딩할 수 있습니다. 바인딩 참조는 Microsoft .NET Framework 리플렉션이나 ICustomTypeDescriptor를 사용하여 확인합니다. 다음은 바인딩의 개체 참조를 확인하는 세 가지 방법입니다.

첫 번째 방법에서는 리플렉션을 사용합니다. 이 경우 PropertyInfo 개체를 사용하여 속성의 특성을 검색하고 속성 메타데이터에 대한 액세스를 제공합니다. ICustomTypeDescriptor 인터페이스를 사용할 경우 데이터 바인딩 엔진은 이 인터페이스를 통해 속성 값에 액세스합니다. ICustomTypeDescriptor 인터페이스는 개체에 정적 속성 세트가 없는 경우에 특히 유용합니다.

INotifyPropertyChanged 인터페이스를 구현하거나 TypeDescriptor와 연결된 변경 알림을 사용하여 속성 변경 알림을 제공할 수 있습니다. 그러나 INotifyPropertyChanged를 사용하면 속성 변경 알림을 더 효율적으로 구현할 수 있습니다.

원본 개체가 CLR 개체이고 원본 속성이 CLR 속성인 경우 WPF(Windows Presentation Foundation) 데이터 바인딩 엔진에서는 먼저 원본 개체에 대해 리플렉션을 사용하여 TypeDescriptor를 가져온 다음, PropertyDescriptor에 대해 쿼리해야 합니다. 이러한 일련의 리플렉션 작업은 너무 많은 시간을 소비할 수 있으므로 성능 면에서 좋지 않습니다.

개체 참조를 확인하는 두 번째 방법에서는 INotifyPropertyChanged 인터페이스를 구현하는 CLR 원본 개체와 CLR 속성인 원본 속성을 사용합니다. 이 경우 데이터 바인딩 엔진에서는 소스 형식에 대해 직접 리플렉션을 사용하여 필요한 속성을 가져옵니다. 이 또한 최적의 방법은 아니지만 첫 번째 방법보다 작업 집합 요구 사항이 적어 비용이 적게 듭니다.

개체 참조를 확인하는 세 번째 방법에서는 DependencyObject인 원본 개체와 DependencyProperty인 원본 속성을 사용합니다. 이 경우 데이터 바인딩 엔진에서 리플렉션을 사용할 필요가 없습니다. 대신 속성 엔진과 데이터 바인딩 엔진이 함께 속성 참조를 독립적으로 확인합니다. 이것이 데이터 바인딩에서 사용되는 개체 참조를 확인하는 최적의 방법입니다.

아래 표에서는 이 세 가지 방법을 사용하여 TextBlock 요소 1,000개의 Text 속성에 데이터 바인딩할 때의 속도를 비교합니다.

TextBlock의 Text 속성 바인딩 대상 바인딩 시간(ms) 렌더링 시간 -- 바인딩 시간 포함(ms)
CLR 개체의 속성 115 314
INotifyPropertyChanged를 구현하는 CLR 개체의 속성 115 305
DependencyObjectDependencyProperty 90 263

대형 CLR 개체에 바인딩

수천 개의 속성이 포함된 단일 CLR 개체에 데이터 바인딩하는 경우 성능에 상당한 영향을 미칩니다. 이 단일 개체를 적은 수의 속성을 포함하는 여러 CLR 개체로 나누어 이러한 영향을 최소화할 수 있습니다. 아래 표에서는 하나의 큰 CLR 개체와 여러 개의 작은 개체에 데이터 바인딩할 때의 바인딩 시간과 렌더링 시간을 비교해서 보여 줍니다.

1000개의 TextBlock 개체 데이터 바인딩 대상 바인딩 시간(ms) 렌더링 시간 -- 바인딩 시간 포함(ms)
1000개 속성이 있는 CLR 개체 950 1200
하나의 속성이 있는 1000개 CLR 개체 115 314

ItemsSource에 바인딩

ListBox에 표시할 직원 목록이 저장된 CLR List<T> 개체가 있는 시나리오를 고려해 보겠습니다. 이 두 개체 간의 대응 관계를 만들려면 직원 목록을 ListBoxItemsSource 속성에 바인딩합니다. 그런데 그룹에 새 직원이 가입했다고 가정합니다. 이 새 직원을 바인딩된 ListBox 값에 삽입하려는 경우 이 직원을 직원 목록에 추가하기만 하면 데이터 바인딩 엔진에서 이 변경 내용을 자동으로 인식할 것으로 생각할 수도 있습니다. 그러나 이 가정은 잘못된 것입니다. 실제로는 변경 내용이 ListBox에 자동으로 반영되지 않습니다. 이는 CLR List<T> 개체에서 컬렉션 변경 이벤트를 자동으로 발생시키지 않기 때문입니다. ListBox에서 변경 내용을 반영하게 하려면 직원 목록을 다시 만들어 ListBoxItemsSource 속성에 다시 연결해야 합니다. 이 솔루션은 효과는 있지만 성능에 큰 영향을 줍니다. ListBoxItemsSource를 다시 할당할 때마다 ListBox에서는 먼저 이전 항목을 throw하고 전체 목록을 다시 생성합니다. ListBox가 복잡한 DataTemplate에 매핑되어 있는 경우에는 성능에 더 큰 영향을 미칩니다.

이 문제에 대한 효율적인 해결 방법은 직원 목록을 ObservableCollection<T>로 만드는 것입니다. ObservableCollection<T> 개체에서는 데이터 바인딩 엔진에서 수신할 수 있는 변경 알림을 발생시킵니다. 이 이벤트는 전체 목록을 다시 생성하지 않고 ItemsControl의 항목을 추가하거나 제거합니다.

아래 표에서는 한 항목을 추가할 때 ListBox 업데이트에 걸리는 시간을 보여 줍니다(UI 가상화를 끈 경우). 첫 행의 숫자는 CLR List<T> 개체를 ListBox 요소의 ItemsSource에 바인딩하는 경우 경과된 시간을 나타냅니다. 두 번째 행의 숫자는 ObservableCollection<T>ListBox 요소의 ItemsSource에 바인딩하는 경우 경과된 시간을 나타냅니다. ObservableCollection<T> 데이터 바인딩 전략을 사용하면 상당한 시간이 절약됨을 알 수 있습니다.

ItemsSource 데이터 바인딩 대상 1개 항목의 업데이트 시간(ms)
CLR List<T> 개체 1656
ObservableCollection<T> 20

IList를 IEnumerable이 아닌 ItemsControl에 바인딩

IList<T> 또는 IEnumerable 중에서 ItemsControl 개체에 바인딩할 항목을 선택하는 경우 IList<T> 개체를 선택합니다. IEnumerableItemsControl에 바인딩하면 WPF가 래퍼 IList<T> 개체를 강제로 만듭니다. 따라서 두 번째 개체의 불필요한 오버헤드로 인해 성능에 영향을 줍니다.

데이터 바인딩만을 위해 CLR 개체를 XML로 변환 안 함

WPF에서는 XML 콘텐츠에 데이터 바인딩할 수 있지만 XML 콘텐츠의 데이터 바인딩은 CLR 개체의 데이터 바인딩보다 느립니다. 데이터 바인딩만을 위해 CLR 개체 데이터를 XML로 변환하지 마세요.

참고 항목