AXForum  
Вернуться   AXForum > Microsoft Dynamics AX > DAX: База знаний и проекты
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск Все разделы прочитаны

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 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).
Старый 20.04.2009, 22:54   #2  
sukhanchik is offline
sukhanchik
Administrator
Аватар для sukhanchik
MCBMSS
Злыдни
Лучший по профессии 2015
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
3,271 / 3465 (122) ++++++++++
Регистрация: 13.06.2004
Адрес: Москва
Перенес в базу знаний
__________________
Возможно сделать все. Вопрос времени
Старый 21.04.2009, 09:33   #3  
Sergey Petrov is offline
Sergey Petrov
Участник
 
80 / 19 (1) ++
Регистрация: 03.04.2007
Адрес: Saint-Petersburg, Russia
Может быть, где-то уже упоминалось, но, думаю, к данной теме это замечание имеет некоторое отношение. Суть замечания - если метод А некоторого класса должен возвращать коллекцию (столкнулся на практике с Set), то бесполезно возвращать сам объект Set. Нужно возвращать контейнер, получаемый в результате выполнения метода pack(). А там, где вызывается этот самый метод А, нужно получать контейнер и с его помощью создавать коллекцию (с помощью статического метода create()).
Старый 21.04.2009, 10:04   #4  
sukhanchik is offline
sukhanchik
Administrator
Аватар для sukhanchik
MCBMSS
Злыдни
Лучший по профессии 2015
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
3,271 / 3465 (122) ++++++++++
Регистрация: 13.06.2004
Адрес: Москва
Это актуально, когда идет передача между клиентом и сервером. В противном случае это формально неважно. Но чтобы можно было делить вызовы между клиентом и сервером не проверяя как передаются объекты - лучше передавать контейнер.

У меня были случаи когда я передавал именно сами объекты без упаковки. Это конечно было больше исключением и обязательно в рамках клиента или сервера.
__________________
Возможно сделать все. Вопрос времени
Старый 21.04.2009, 10:37   #5  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
Повозился. Интересно. Автору - респект за раскопки! Когда я начинал изучать классы-коллекции, то метод create выглядел очень заманчиво, но быстро понял, что это не совсем то, что хотелось (а хотелось именно при инициализации в той же строке хвост значений прописать)... Но оказывается, всё-таки, это именно ТО! Нужно было только дотумкать до структуры контейнера

Имею некоторые дополнения. В ходе возни первым делом проверил обеспечение уникальности Set. Обеспечивается! И без ругани. Если мы возьмем для Set контейнер (ту часть, которая собственно данные) вида ['Mum','washed','Mum','frame'], то после загрузки Set, как и должно, будет содержать 3 уникальных элемента в отсортированном виде: {'frame','Mum','washed'}.
X++:
static void Check_SetUnique(Args _args)
{
    Set setByInit10 = Set::create( [ 1, any2int(Types::String), 10] 
                                   + ['Mum','washed','Mum','frame']) ; 
                                   // данные не уникальны!
    Set setByInit3  = Set::create( [ 1, any2int(Types::String), 3 ] 
                                   + ['Mum','washed','Mum','frame']) ;
    ;
    info(strFmt('setByInit10 : %1', setByInit10.toString()));
    info(strFmt('setByInit10.elements = %1', setByInit10.elements()));

    info(strFmt('setByInit3  : %1', setByInit3 .toString()));
    info(strFmt('setByInit3.elements = %1', setByInit3.elements()));
}
Код:
ИНФОЛОГ:

setByInit10 : {"frame", "Mum", "washed"}
setByInit10.elements = 3
setByInit3  : {"Mum", "washed"}
setByInit3.elements = 2
Еще обратите внимание на количество элементов в заголовке. Оказывается, не нужно скрупулезно считать, сколько элементов содержится в контейнере перед инициализацией, достаточно указать какое-нибудь заведомо большее число (см. setByInit10). Это число элементов - set.elements() - после инициализации будет правильно пересчитано по факту.

Но если длина контейнера данных указана меньше необходимой, что вначале будет отобрано заданное кол-во элементов (без проверки на уникальность! просто последовательно "слева направо"!), затем будут отброшены дубликаты и наконец выполнена сортировка - см. setByInit3, которое, несмотря на казалось бы дозволенность трех элементов, содержит их только два.
Старый 21.04.2009, 11:27   #6  
kashperuk is offline
kashperuk
Участник
Аватар для kashperuk
MCBMSS
Соотечественники
Сотрудники Microsoft Dynamics
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии 2011
Лучший по профессии 2009
 
4,361 / 2084 (78) +++++++++
Регистрация: 30.05.2004
Адрес: Atlanta, GA, USA
Да, я когда-то такой подход с контейнерами и Create() использовал для удаления элементов из класса List. (в стандартном приложении это можно сделать только с помощью класса ListIterator)
Вроде бы даже этот способ был быстрее, но я уже не очень помню, давно было
Старый 21.04.2009, 14:18   #7  
gl00mie is offline
gl00mie
Участник
MCBMSS
Most Valuable Professional
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
3,684 / 5788 (200) ++++++++++
Регистрация: 28.11.2005
Адрес: Москва
Записей в блоге: 3
Еще одно маленькое дополнение: выходит, можно легко получить множество уникальных значений списка:
X++:
List    list = new List( Types::String );
Set     set;
// ...
set = Set::create( list.pack() );
За это сообщение автора поблагодарили: Lemming (1), Logger (3), alex55 (3).
Старый 29.09.2014, 13:48   #8  
gl00mie is offline
gl00mie
Участник
MCBMSS
Most Valuable Professional
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
3,684 / 5788 (200) ++++++++++
Регистрация: 28.11.2005
Адрес: Москва
Записей в блоге: 3
Уточнение для AX 2012 и выполнения кода в CIL
Очень полезная тема создания классов-коллекций из контейнеров требует уточнения для AX 2012. Из моего скромного опыта, надо учесть, что:
  • номер версии (первый элемент контейнера) теперь не 1, а 3, хотя с 1 тоже все работает;
  • классы-коллекции очень чувствительны к базовому типу служебных значений в контейнере при работе в CIL, в частности, к количеству элементов, которое указывается после базового типа элементов.
О последнем обстоятельстве я узнал на таком примере: есть исходные данные для создания Map (пары ключ-значение), которые зашиты в макрос, в коде по макросу создается контейнер с данными, затем формируется контейнер со служебной частью для Map, куда 4-м элементом пишется длина контейнера с данными, деленная надвое. Для деления использовался оператор div, который, как выяснилось, ведет себя в CIL не так, как в интерпретаторе X++. В итоге выражение
X++:
conLen(con) div 2
в X++ давало значение Integer, а в CIL - Int64, при этом в X++ Map'у без разницы тип счетчика элементов - Integer или Int64, а вот в CIL Map генерит исключение "Specified cast is not valid". Поскольку дело происходило в коде, дергавшемся из AIF лишь для определенных входных данных, то я долго не мог понять, где же именно генерится это исключение. В итоге пришлось сделать явное приведение типа, чтобы все корректно работало в CIL:
X++:
any2int(conLen(con) div 2)
За это сообщение автора поблагодарили: Logger (3), S.Kuskov (2).
Старый 25.05.2018, 09:29   #9  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,860 / 3109 (111) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Цитата:
Сообщение от gl00mie Посмотреть сообщение
Еще одно маленькое дополнение: выходит, можно легко получить множество уникальных значений списка:
X++:
List    list = new List( Types::String );
Set     set;
// ...
set = Set::create( list.pack() );
Интересно.
А если такое же проделать с мапом и листом то падает аксапта. Для серверного кода падает аос.

X++:
// PKoz 24.05.2018

// проверяем глюк с падением аксапты
// воспроизводится на 4.0 / 2009 / 2012 R3
static void JEV002611_2(Args _args)
{
    List        list;
    Map         map;

    container   packed;
    ;

    if (Box::yesNo("Роняем Аксапту ?", DialogButton::No) == DialogButton::No)
    {
        return;
    }

    map = new Map(Types::String, Types::Container);
    map.insert("куку",     [1, 2, 3]);
    map.insert("кукареку", [1, 2, 3, 4]);

    packed = map.pack();

    list = List::create(packed);
    // list = List::create(connull());
    info("Ура! Не упали.");
}
За это сообщение автора поблагодарили: S.Kuskov (5), gl00mie (3).
Старый 25.05.2018, 10:22   #10  
belugin is offline
belugin
Участник
Аватар для belugin
Сотрудники Microsoft Dynamics
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии 2011
Лучший по профессии 2009
 
4,622 / 2922 (107) +++++++++
Регистрация: 16.01.2004
Записей в блоге: 5
Это же грязный хак. Почему никто не предупреждает. Тут могут быть дети!
За это сообщение автора поблагодарили: Logger (1).
Старый 04.06.2018, 18:45   #11  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,860 / 3109 (111) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Написали свои конструкторы для создания map / list / set из контейнера. Аос не падает. Выбрасывает исключение.

Упаковку классов не проверял. Только базовые типы.
Вложения
Тип файла: zip Collections_dev.zip (8.6 Кб, 331 просмотров)
За это сообщение автора поблагодарили: mazzy (5), Kurol (1).
Теги
container, faq, list, map, set, классы коллекций, полезное, pack, unpack

 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
Kashperuk Ivan: List panels in Dynaics AX - a short description of SysListPanel class Blog bot DAX Blogs 1 21.10.2007 22:51
dax-dilettante: 15. System Classes \ The Collection Classes Blog bot DAX Blogs 0 26.09.2007 23:51
casperkamal: Creating a Custom Display List - Blog bot DAX Blogs 0 27.12.2006 17:20
Инициализация map axaLearner DAX: Программирование 3 24.08.2004 15:41
Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 10:58.
Powered by vBulletin® v3.8.5. Перевод: zCarot
Контактная информация, Реклама.