Участник
Регистрация: 30.08.2005
Адрес: Tyumen
|
Классы коллекций (инициализация, сериализация): List, Set, Map.
Некоторое время назад (после выхода Dynamics Ax 4.0) набросал для себя заметки о структуре упакованных в контейнер данных возвращаемых методом set.pack(). Недавно 'нашел' эти заметки, кое-что из них перепроверил для AX2009 (RTM), + добавил информацию и примеры для list и map. Вот что получилось в итоге:
Просматривая штатную документацию по классам коллекций ( List, Set, Map - далее по тексту Collections) в Ах обратил внимание на описание структуры контейнеров используемых для сериализации в методах collections. pack() и Collections:: create(container )
List Class
Цитата:
The container created by this method contains 3 elements before the first element from the list:
- A version number for the container
- An integer that identifies the data type of the list elements
- The number of elements in the list
Set Class
Цитата:
The container created by this method contains 3 elements before the first element from the set:
- A version number for the container
- An integer that identifies the data type of the set elements
- The number of elements in the set
Map Class
Цитата:
The container created by this method contains 4 elements before the first element from the map:
- A version number for the container
- An integer that identifies the data type of the keys in the map
- An integer that identifies the data type of the values in the map
- The number of elements in the map
т.е. структура упакованных объектов в контейнер примерно идентична и состоит из 2 частей: заголовок и непосредственно сами данные - [ packedContainer] = [ <header>] + [ <elements>]
где [ <header>] можно представить как: - [ <CurrentVersion>(integer), <тип элемента - elementTypeId>(integer), <число упакованных элементов - elementCount>(integer)] для List и Set
- [ <CurrentVersion>(integer), <тип ключа - keyTypeId>(integer), <тип значения - elementTypeId>(integer), <число упакованных элементов - elementCount>(integer)] для Map
(значения keyTypeId и elementTypeId - числовые коды перечисления ( base enum) Types используемые при создании экземпляров классов коллекций).
Проверка кода:
X++: info( strfmt( "List packed version: %1", conpeek( new List( Types::Integer ).pack(), 1 ) ) ) ;
info( strfmt( "Set packed version: %1", conpeek( new Set( Types::Integer ).pack(), 1 ) ) ) ;
info( strfmt( "Map packed version: %1", conpeek( new Map( Types::Integer, Types::Integer ).pack(), 1 ) ) ) ; на приложениях Axapta 3.0 SP5, Dynamics Ax 4.0 SP2, Dynamics AX2009 выдала идентичные результаты:
Цитата:
List packed version: 1
Set packed version: 1
Map packed version: 1
P.S. Понятно, что с изменением алгоритма(структуры) упаковки данных, номер версии контейнера может измениться.
и где [ <elements>] - [ element1, element2, ..., elementN ] для List и Set
- [ key1, value1, key2, value2,..., keyN, valueN ] - пары значений element(key, value) для Map
Для типа элементов Types::Class классов коллекций Collections секция [ <elements>] хранит значения в виде пар <classId, [packed class data]>: <element_N> = <classId_N, [packed class data_N]>
X++: static void jbListClasses(Args _args)
{
Set setDemo = new Set( Types::Date ) ;
Map mapDemo = new Map( Types::Integer, Types::String) ;
List listOfClasses = new List( Types::Class ) ;
void showContainerInfo( container _con )
{
str valueAsString ;
str totalAsString ;
int iCount = conlen( _con ) ;
int idx ;
for( idx=1; idx<= iCount; idx++ )
{
if( typeof( conpeek( _con, idx ) ) != Types::Container )
valueAsString = strfmt( "%1", conpeek( _con, idx ) ) ;
else valueAsString = strfmt( "[%1]", con2str( conpeek( _con, idx ) ) ) ;
if( totalAsString )
totalAsString += ';' ;
totalAsString += valueAsString ;
info( strfmt( "position: %1, value: %2", idx, valueAsString ) );
}
info( strfmt( "[%1]", totalAsString ) ) ;
}
;
setDemo.add( 01\01\2009 ) ;
setDemo.add( systemdateget() ) ;
mapDemo.insert( 1, 'one' ) ;
mapDemo.insert( 2, 'two' ) ;
listOfClasses.addStart( new List( Types::Integer ) ) ;
listOfClasses.addEnd( setDemo ) ;
listOfClasses.addEnd( mapDemo ) ;
listOfClasses.addEnd( SalesTable2LineField::construct( fieldNum( SalesTable, Dimension ) ) ) ;
showContainerInfo( listOfClasses.pack() ) ;
} Результат для Dynamics Ax 4.0 ( скобками '{' '}' и подчеркиванием отмечены блоки <header> и <elements> контейнеров с упакованными данными):
Цитата:
position: 1, value: 1
// <header> listOfClasses - version
position: 2, value: 10
// <header> listOfClasses - Types::Class
position: 3, value: 4
// <header> listOfClasses - listOfClasses.elements()
position: 4, value: 65231
// <elements> listOfClasses[1,1] - classNum( List )
position: 5, value: [1,1,0]
// <elements> listOfClasses[1,2] - List.pack()
position: 6, value: 65238
// <elements> listOfClasses[2,1] - classNum( Set )
position: 7, value: [1,3,2,2009.01.01,2009.04.17]
// <elements> listOfClasses[2,2] - Set.pack() : [{1,3,2},{2009.01.01},{2009.04.17}]
position: 8, value: 65236
// <elements> listOfClasses[3,1] - classNum( Map )
position: 9, value: [1,1,0,2,1,one,2,two]
// <elements> listOfClasses[3,2] - Map.pack() : [{1,1,0,2},{1,one},{2,two}]
position: 10, value: 4512
// <elements> listOfClasses[4,1] - classNum( SalesTable2LineField )
position: 11, value: [1,23,0]
// <elements> listOfClasses[4,2] - SalesTable2LineField.pack()
[1;10;4;65231;[1,1,0];65238;[1,3,2,2009.01.01,2009.04.17];65236;[1,1,0,2,1,one,2,two];4512;[1,23,0]]
// [{1;10;4};{65231;[1,1,0]};{65238;[1,3,2,2009.01.01,2009.04.17]};{65236;[1,1,0,2,1,one,2,two]};{4512;[1,23,0]}]
Для успешной сериализации объектов классов коллекций для типа элементов Types::Class необходимо придерживаться Pack-Unpack Design Pattern для классов помещаемых в коллекцию.
Зная структуру упакованного состояния класса коллекции можно используя Collections:: create() эмулировать объявление экземпляра класса коллекции с инициализацией значений (передавая в качестве параметра сформированыый контейнер). Тут стоит оговориться, что при изменении разработчиками структуры упаковки состояния классов Collections ниже приведенные примеры скорее всего не будут работать.
На примере элементарных(встроенных) типах данных в X++, код:
X++: Set setDemo = new Set( Types::String) ;
;
setDemo.add( 'Mum' ) ;
setDemo.add( 'washed' ) ;
setDemo.add( 'a' ) ;
setDemo.add( 'frame') ; можно заменить объявлением:
X++: Set setDemo = Set::create( [ 1, any2int(Types::String), 4 ] + [ 'Mum', 'washed', 'a', 'frame' ] ) ;
// Set setDemo = Set::create( [ 1, 0, 4, 'Mum', 'washed', 'a', 'frame' ] ) ; // Types::String = 0 Пример job'а для Set:
X++: static void jbSetInitDemo(Args _args)
{
Set setByInit = Set::create( [ 1, any2int(Types::String), 4 ] + [ 'Mum', 'washed', 'a', 'frame' ] ) ;
Set setByCode = new Set( Types::String ) ;
void showSet( Set _set, TempStr _prefix = '' )
{
SetEnumerator setEnumerator = _set.getEnumerator() ;
;
setPrefix( _prefix ) ;
while( setEnumerator.moveNext() )
info( strfmt( "%1", setEnumerator.current() ) ) ;
}
;
setByCode.add( 'Mum' ) ;
setByCode.add( 'washed' ) ;
setByCode.add( 'a' ) ;
setByCode.add( 'frame') ;
info( 'set elements' ) ;
showSet( setByCode, 'inserted by code' ) ;
showSet( setByInit, 'inserted by create' ) ;
} и List:
X++: static void jbListInitDemo(Args _args)
{
List listByInit = List::create( [ 1, any2int(Types::String), 4 ] + [ 'Mum', 'washed', 'a', 'frame' ] ) ;
List listByCode = new List( Types::String ) ;
void showList( List _list, TempStr _prefix = '' )
{
ListEnumerator listEnumerator = _list.getEnumerator() ;
;
setPrefix( _prefix ) ;
while( listEnumerator.moveNext() )
info( strfmt( "%1", listEnumerator.current() ) ) ;
}
;
listByCode.addStart( 'Mum' ) ;
listByCode.addEnd( 'washed' ) ;
listByCode.addEnd( 'a' ) ;
listByCode.addEnd( 'frame') ;
info( 'list elements' ) ;
showList( listByCode, 'inserted by code' ) ;
showList( listByInit, 'inserted by create' ) ;
} В случае контейнерных элементов:
X++: Set setDemo = new Set( Types::Container ) ;
;
setDemo.add( [ 'a', 'b', 'c', 'd' ] ) ;
setDemo.add( [ 'e', 'f', 'g', 'h' ] ) ;
setDemo.add( [ 1, 2, 3, 4 ] ) ; можно заменить 'эквивалентным' кодом :
X++: Set setDemo = Set::create( [ 1, any2int(Types::Container), 3 ] + [ [ 'a', 'b', 'c', 'd' ], [ 'e', 'f', 'g', 'h' ], [ 1, 2, 3, 4 ] ] ) ; Пример инициализации Map'а программно :
X++: Map mapDemo = new Map( Types::Integer, Types::Container ) ;
;
mapDemo.insert( 1, [ 'one', 'single' ] ) ;
mapDemo.insert( 2, [ 'two', 'double' ] ) ;
mapDemo.insert( 3, [ 123, 456 ] ) ;
mapDemo.insert( 4, [ ABC::C ] ) ; и его 'аналог':
X++: Map mapDemo = Map::create( [ 1, any2int(Types::Integer), any2int(Types::Container), 4 ]
+ [ 1, [ 'one', 'single' ] ]
+ [ 2, [ 'two', 'double' ] ]
+ [ 3, [ 123, 456 ] ]
+ [ 4, [ ABC::C ] ] ) ; X++: static void jbMapInitDemo(Args _args)
{
Map mapByCode = new Map( Types::Integer, Types::Container ) ;
Map mapByInit = Map::create( [ 1, any2int(Types::Integer), any2int(Types::Container), 4 ]
+ [ 1, [ 'one', 'single' ] ]
+ [ 2, [ 'two', 'double' ] ]
+ [ 3, [ 123, 456 ] ]
+ [ 4, [ 123.456 ] ] ) ;
void showMap( Map _map, TempStr _prefix = '' )
{
MapEnumerator mapEnumerator = _map.getEnumerator() ;
;
setPrefix( _prefix ) ;
while( mapEnumerator.moveNext() )
info( strfmt( "key: %1, value: [%2]", mapEnumerator.currentKey(), con2str( mapEnumerator.currentValue() ) ) );
}
;
mapByCode.insert( 1, [ 'one', 'single' ] ) ;
mapByCode.insert( 2, [ 'two', 'double' ] ) ;
mapByCode.insert( 3, [ 123, 456 ] ) ;
mapByCode.insert( 4, [ 123.456 ] ) ;
info( 'map elements' ) ;
showMap( mapByCode, 'inserted by code' ) ;
showMap( mapByInit, 'inserted by create' ) ;
}
|