Показать сообщение отдельно
Старый 20.04.2009, 16:58   #1  
petergunn is offline
petergunn
Участник
 
118 / 274 (10) ++++++
Регистрация: 30.08.2005
Адрес: Tyumen
Post Классы коллекций (инициализация, сериализация): 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' ) ;
}
За это сообщение автора поблагодарили: mazzy (5), sukhanchik (10), Logger (8), gl00mie (10), Gustav (5), Jorj (1), plumbum (1), in.dc (2).