Delphi 델파이 RTL: 새로운 제네릭 컬렉션 vs 기존 클래식 컬렉션
2020.01.06 12:47
Marco Cantu의 블로그에서 흥미로운 주제가 있어서 번역하였습니다. 본문에는 해당 요소들에 대한 링크가 있어서 더 깊이있게 이해할 수 있습니다.
제네릭 타입은 델파이 2009 버전에 추가되었습니다. 델파이의 런타임 라이브러리 (RTL)에는 제네릭 컬렉션 유닛이 들어간 것도 이때부터입니다. 하지만 RTL에는 제네릭 컬렉션 뿐만 아니라 기존에 있던 클래식 컬렉션도 여전히 남아 있습니다. 최근에 저는 예전 컬렉션과 새 컬렉션 중 어느 것을 사용하면 더 좋은 지에 대한 질문을 받고 심도있는 토론을 이어갔습니다. 이 토론의 개요를 이 글로 정리합니다.
클래식 컨테이너 클래스
RTL에 있는 가장 기본적인 컬렉션 클래스는 (System.Classes 유닛에 있는) TList 클래스입니다. 이것은 포인터들 (또는 개체의 참조들)을 담는 일반적인 리스트입니다. 같은 계열인(System.Contnrs 유닛에 있는) 클래식 컨테이너 클래스(들)로는 다음과 같은 것들이 있습니다:
- TObjectList는 TObject의 참조들을 담는 TList로, 객체에 대한 소유권(Ownership)* 지원을 포함
- TComponentList 또한 소유권을 가지며, 컴포넌트 알림(Notification: 생성 및 해제 알림)**을 지원
- TClassList
- TOrderedList
- TStack과 TQueue, TObjectStack과 TObjectQueue
- 다양한 BucketList(해시 테이블)
위 컨테이너들을 사용할 때에는 종종 여러분이 다루고 있는 개체의 타입을 변환(cast)하여 해당 리스트가 지원해주는 타입에 맞추게 되기 때문에 오류가 생길 가능성이 발생합니다. 만약 여러분이 해당 리스트에서 꺼낸 개체의 타입을 계속 확인하기 위해 “as” 와 같이 동적 변환(dynamic cast)을 사용한다면 프로그램 실행이 느려지는 원인이 되기도 합니다.
myList: TList; myList.Get(3) as TMyObject
소유권(Ownership)*: 소유권을 위임하면 소유주(Owner) 객체 해재시 소유권을 위임한 객체를 함께 해제합니다. 즉 TObjectList에 소유권 위임 설정(OwnsObjects := True)하면 TObjectList 해제 시 등록된 객체를 함께 해제합니다.
컴포넌트 알림(Notification)**: TComponent는 Notification 메소드를 제공합니다. Notification 메소드는 소유하고 있는 모든 컴포넌트에 소유권 추가와 삭제 메시지를 전달합니다.
제네릭 컬렉션 클래스
언어에 제네릭 타입(들)이 추가되면서 델파이에는 제네릭 컬렉션(들) 기본 세트가 추가되었습니다. 이 세트는 System.Generics.Collections 유닛에 있으며, 컨테이너에 담고 싶은 데이터 타입을 여러분이 직접 지정할 수 있도록, 제네릭한 (즉, 컨테이너에 담길 타입이 사전에 특정되지 않은) 컨테이너의 밑그림을 제공합니다. 제네릭 컬렉션(들)에는 다음과 같은 것들이 있습니다:
- TList<T>, 기본 제네릭 리스트로 정렬과 열거(enumeration) 지원합니다.
- TThreadList<T>, 쓰래드에 안전한 리스트로서 잠금(locking)을 지원합니다.
- TQueue<T>, TStack<T>
- TDictionary<TKey,TValue>, 상당히 막강한 딕셔너리이며, 키(key)와 값(value)의 타입을 필요에 맞게 지정할 수 있습니다.
- TObjectList<T: class> 객체 타입을 담을 수 있으며 소유권을 지원합니다.(아래의 컨테이너들도 동일)
- TObjectQueue<T: class>, TObjectStack<T: class>
- TObjectDictionary<TKey,TValue>
- TThreadedQueue<T>
제네릭 컬렉션의 장점
제네릭스 컬렉션(들)을 사용하면 여러분이 사용하고자 하는 데이터 타입을 담을 특정 컨테이너(들)을 정의할 수 있습니다. 그러면 해당 데이터 타입에 부합하는 지를 컴파일러가 확인하므로 실행 중에 데이터 타입을 확인하지 않아도 됩니다. 그 결과, 코드가 더 깔끔하고 읽기도 더 쉽습니다. 애플리케이션은 더 견고해지고, 실행 시점에 확인할 필요가 줄어들기 때문에 실행 속도도 빨라집니다:
myList: TList <TMyObject>; myList.Get(3); // TMyObject 반환
델파이에서 새로 추가되는 라이브러리와 서브시스템들은 모두 제네릭 컬렉션(들)을 사용합니다. 예를 들어 파이어몽키는 전통적인 코딩 스타일이 아니라 제네릭 컬렉션(들)을 주로 활용합니다.
제네릭 컬렉션을 사용하면 안될 이유는 어떤것이 있나요?
제네릭 컬렉션(들)을 사용하면 안되는 경우는 기본적으로 다음 두 가지가 있습니다.
첫째, 작동하고 있는 기존 코드가 있다면 여러분은 굳이 변경하고 싶지 않을 것입니다. 특히 VCL과 같은 라이브러리에 들어있는 기존 코드를 변경하면, 해당 라이브러리를 사용하는 코드(들)에서 타입 불일치 문제가 발생될 수 있습니다.
둘째, 델파이에 있는 제네릭(들)은 상당한 코드 비대화(Code bloat)을 유발합니다. 컬렉션에서 사용되는 데이터 타입이 거의 같더라도 다양한 데이터 타입을 위한 메소드들이 매번 복제되기 때문입니다. 이점은 굉장히 규모가 큰 애플리케이션에서라면 컴파일 시간과 링크 시간 그리고 실행 파일 크기 측면에서 부정적인 영향을 끼칩니다.
또 다른 제네릭 컬렉션
마지막으로, 전하고 싶은 점이 있습니다. 만약 여러분이 제네릭 컬렉션(들)을 상당히 많이 사용한다면 스프링4D (Spring4D) 라이브러리에 있는 것들을 고려하시기 바랍니다. 이 라이브러리는 RTL에 있는 것들을 확장한 것입니다. 관련 도움말은 https://bitbucket.org/sglienke/spring4d/wiki/Collections 에 있습니다.
다시 말씀드리지만 이 글은 짧은 요약입니다. 훨씬 더 광범위하고 세밀하게 설명할 수도 있겠으나 델파이의 제네릭 컬렉션(들)을 충분히 알지 못해서 예전 방식만 사용하고 있는 개발자들이 있다면 이 요약 만으로도 도움이 되기를 바랍니다.
참고자료
- 원문 : Delphi RTL: Generic vs. Traditional Collections
- 더 쉬운코드, 더 유연한 코드를 작성할 수 있는 현대식 문법
- 제네릭과 타입 헬퍼, 익명메소드에 대한 소개와 데모 코드를 살펴볼 수 있습니다.