20.04.2009, 16:58 | #1 |
Участник
|
Классы коллекций (инициализация, сериализация): 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 Цитата:
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 Цитата:
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 где [<header>] можно представить как:
Проверка кода: 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 ) ) ) ; Цитата:
List packed version: 1
Set packed version: 1 Map packed version: 1 и где [<elements>]
Для типа элементов 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() ) ; } Цитата:
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]}] Зная структуру упакованного состояния класса коллекции можно используя 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 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' ) ; } 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 ] ] ) ; 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 |
Administrator
|
Перенес в базу знаний
__________________
Возможно сделать все. Вопрос времени |
|
21.04.2009, 09:33 | #3 |
Участник
|
Может быть, где-то уже упоминалось, но, думаю, к данной теме это замечание имеет некоторое отношение. Суть замечания - если метод А некоторого класса должен возвращать коллекцию (столкнулся на практике с Set), то бесполезно возвращать сам объект Set. Нужно возвращать контейнер, получаемый в результате выполнения метода pack(). А там, где вызывается этот самый метод А, нужно получать контейнер и с его помощью создавать коллекцию (с помощью статического метода create()).
|
|
21.04.2009, 10:04 | #4 |
Administrator
|
Это актуально, когда идет передача между клиентом и сервером. В противном случае это формально неважно. Но чтобы можно было делить вызовы между клиентом и сервером не проверяя как передаются объекты - лучше передавать контейнер.
У меня были случаи когда я передавал именно сами объекты без упаковки. Это конечно было больше исключением и обязательно в рамках клиента или сервера.
__________________
Возможно сделать все. Вопрос времени |
|
21.04.2009, 10:37 | #5 |
Moderator
|
Повозился. Интересно. Автору - респект за раскопки! Когда я начинал изучать классы-коллекции, то метод 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 Но если длина контейнера данных указана меньше необходимой, что вначале будет отобрано заданное кол-во элементов (без проверки на уникальность! просто последовательно "слева направо"!), затем будут отброшены дубликаты и наконец выполнена сортировка - см. setByInit3, которое, несмотря на казалось бы дозволенность трех элементов, содержит их только два. |
|
21.04.2009, 11:27 | #6 |
Участник
|
Да, я когда-то такой подход с контейнерами и Create() использовал для удаления элементов из класса List. (в стандартном приложении это можно сделать только с помощью класса ListIterator)
Вроде бы даже этот способ был быстрее, но я уже не очень помню, давно было |
|
21.04.2009, 14:18 | #7 |
Участник
|
Еще одно маленькое дополнение: выходит, можно легко получить множество уникальных значений списка:
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 |
Участник
|
Уточнение для AX 2012 и выполнения кода в CIL
Очень полезная тема создания классов-коллекций из контейнеров требует уточнения для AX 2012. Из моего скромного опыта, надо учесть, что:
X++: conLen(con) div 2 X++: any2int(conLen(con) div 2) |
|
|
За это сообщение автора поблагодарили: Logger (3), S.Kuskov (2). |
25.05.2018, 09:29 | #9 |
Участник
|
Цитата:
А если такое же проделать с мапом и листом то падает аксапта. Для серверного кода падает аос. 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 |
Участник
|
Это же грязный хак. Почему никто не предупреждает. Тут могут быть дети!
|
|
|
За это сообщение автора поблагодарили: Logger (1). |
04.06.2018, 18:45 | #11 |
Участник
|
Написали свои конструкторы для создания map / list / set из контейнера. Аос не падает. Выбрасывает исключение.
Упаковку классов не проверял. Только базовые типы. |
|
|
За это сообщение автора поблагодарили: mazzy (5), Kurol (1). |
06.11.2024, 18:25 | #12 |
Участник
|
Привет.
Не уверен насколько удачно выбрал тему для подобного текста. Есть кастомизация - некий фремворк позволяющий выстраивать событийную модель (по мотивам 365). Стандартно: Есть генератор события - некий объект. Есть подписчики - тоже объекты. Список подписчиков должен где-то храниться и исторически так сложилось, что это Set. Всё бы хорошо, да, как принято, в какой-то момент, какой-то там из "генераторов" начал считать сводный hash на основании какой-то информации предоставляемой его подписчиками (описание по верхам). Подписчики, в данном случае, N экземпляров 1-го класса инициализированные с уникальными параметрами. Упрощенный пример: X++: subscriber.add(new Subs(param_1)); subscriber.add(new Subs(param_2)); Просто иногда функция getEnumerator возвращает последовательность: {Subs(param_1), Subs(param_2)}, а иногда {Subs(param_2), Subs(param_1)} Легко воспроизводится в джобе (несколько раз вызвать придётся): X++: Set s = new Set(Types::Class); SetEnumerator se; ; s.add(new Class1(5)); s.add(new Class1(1)); se = s.getEnumerator(); while (se.moveNext()) { info(se.current().toString()); } X++: class Class1 { int j; public void new(int _j = 0) { ; j = _j; } public str toString() { ; return int2str(j); } } Отсюда возникает вопрос, критерий сортировки массива классов какой? Спасибо MS, Set это X++: public class Set : Microsoft.Dynamics.Ax.Xpp.XppObjectBase X++: switch (_Type) { case Types.String: case Types.RString: case Types.VarString: this.set = new SortedSet<object>(CollectionComparerString.Default); return; case Types.Integer: case Types.Enum: case Types.Time: this.set = new SortedSet<object>(CollectionComparerInt.Default); return; case Types.Real: this.set = new SortedSet<object>(CollectionComparerReal.Default); return; case Types.Date: this.set = new SortedSet<object>(CollectionComparerDate.Default); return; case Types.UtcDateTime: this.set = new SortedSet<object>(CollectionComparerDateTime.Default); return; case Types.Container: this.set = new SortedSet<object>(CollectionComparerContainer.Default); return; case Types.Record: this.set = new SortedSet<object>(CollectionComparerRecord.Default); return; case Types.Class: this.set = new HashSet<object>(); return; case Types.Guid: this.set = new SortedSet<object>(CollectionComparerGuid.Default); return; case Types.Int64: this.set = new SortedSet<object>(CollectionComparerInt64.Default); return; } this.set = new SortedSet<object>(CollectionComparer.Default); X++: this.set = new HashSet<object>(); |
|
|
За это сообщение автора поблагодарили: Logger (7), sukhanchik (5). |
06.11.2024, 18:38 | #13 |
Участник
|
Если порядок важен то лучше наверно задействовать Map. Я подозреваю, что GetHashCode считается по ссылке на объект. Т.е. зависит от адреса, поэтому такая случайность в сортировке.
|
|
Теги |
container, faq, list, map, set, классы коллекций, полезное, pack, unpack |
|
|