Согласно документации если мы выполняем цикл по таблице возвращаемой из функционального метода, конструкторных выражений, или табличных выражений, с последующим присвоением к символьным переменным (ASSIGNING) или ссылочным переменным REFERENCE INTO, среда создаёт временную таблицу для данного цикла.
If the internal table is specified as the return value or result of a functional method, a constructor expression, or a table expression, the value is persisted for the duration of the loop. Afterwards, it is no longer possible to access the internal table.
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
CLASS lcl_app DEFINITION. PUBLIC SECTION. TYPES tt_data TYPE STANDARD TABLE OF i WITH EMPTY KEY. DATA mt_data TYPE tt_data. METHODS: get_data RETURNING VALUE(rt_data) TYPE tt_data, change_data. ENDCLASS. CLASS lcl_app IMPLEMENTATION. METHOD get_data. rt_data = mt_data. ENDMETHOD. METHOD change_data. LOOP AT get_data( ) ASSIGNING FIELD-SYMBOL(<lv_data>). <lv_data> = <lv_data> + 1. ENDLOOP. ENDMETHOD. ENDCLASS. START-OF-SELECTION. DATA(lo_app) = NEW lcl_app( ). lo_app->mt_data = VALUE #( ( 1 ) ). lo_app->change_data( ). cl_demo_output=>display( lo_app->mt_data ). |
Далее рассмотрим как это выглядит при анализе используемой памяти в отладке.
На выходе из метода get_data( ) создаётся переменная ссылающаяся на тот же объект в памяти где лежат основные данные — mt_data. Срабатывает так называемый table sharing.
Но уже после первой модификации в цикле среда понимает, что использовать одну и ту же таблицу нельзя и создаёт её копию:
Соответственно mt_data остаётся без изменений.
Данное поведение описано и не вызывает никаких вопросов, однако такое поведение может вызывать некоторый ступор и непонимание, если мы получаем доступ не просто к таблице, но делаем это через ссылку на объект:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
CLASS lcl_app DEFINITION. PUBLIC SECTION. TYPES tt_data TYPE STANDARD TABLE OF i WITH EMPTY KEY. DATA mt_data TYPE tt_data. METHODS: change_data, get_reference RETURNING VALUE(ro_data) TYPE REF TO lcl_app. ENDCLASS. CLASS lcl_app IMPLEMENTATION. METHOD change_data. LOOP AT get_reference( )->mt_data ASSIGNING FIELD-SYMBOL(<lv_value>). <lv_value> = <lv_value> + 1. ENDLOOP. ENDMETHOD. METHOD get_reference. ro_data = me. ENDMETHOD. ENDCLASS. START-OF-SELECTION. DATA(lo_app) = NEW lcl_app( ). lo_app->mt_data = VALUE #( ( 1 ) ). lo_app->change_data( ). cl_demo_output=>display( lo_app->mt_data ). |
Вы можете подумать что возвращая ссылку вы уже получаете доступ по ней, однако ABAP среда так не думает. Результат работы:
Среда понимает, что результат возвращаемый из метода будет использован для цикла и создаёт как и в первом случае временную таблицу в памяти.
На выходе из метода get_reference в памяти создаётся временная ссылочная переменная ссылающаяся на инстанцию lcl_app:
Но после первого изменения табличных данных, согласно правилам table sharing создаётся копия таблицы уже с новой ссылочной переменной:
По сути предыдущий код аналогичен следующему:
1 2 3 4 5 6 7 8 |
... METHOD change_data. DATA(lt_table_copy) = get_reference( )->mt_data. LOOP AT lt_table_copy ASSIGNING FIELD-SYMBOL(<lv_value>). <lv_value> = <lv_value> + 1. ENDLOOP. ENDMETHOD. ... |
Но что делать если мы все таки хотим изменить данные и обойти механизм данной оптимизации?
Обойти это можно следующим способом:
1 2 3 4 5 6 7 |
METHOD change_data. DATA(lo_ref) = get_reference( ). LOOP AT lo_ref->mt_data ASSIGNING FIELD-SYMBOL(<lv_value>). <lv_value> = <lv_value> + 1. ENDLOOP. ENDMETHOD. |
Либо:
1 2 3 4 5 |
METHOD change_data. LOOP AT CAST lcl_app( get_reference( ) )->mt_data ASSIGNING FIELD-SYMBOL(<lv_value>). <lv_value> = <lv_value> + 1. ENDLOOP. ENDMETHOD. |
В первом случае мы явно сохраняем ссылку на объект и уже цикл стартуем по переменной из этого объекта.
Во втором случае после оператора CAST (который по сути ничего не преобразовывает) создаётся временная ссылочная переменная:
Которая ссылается на инстанцию lcl_app. Соответственно данные будут изменены.
Еще один «абстрактный» пример, но уже с изменением через конструкторный оператор:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
CLASS lcl_app DEFINITION. PUBLIC SECTION. TYPES tt_data TYPE STANDARD TABLE OF i WITH EMPTY KEY. DATA mt_data TYPE tt_data. METHODS: change_data, get_reference RETURNING VALUE(ro_data) TYPE REF TO lcl_app, change_value CHANGING cv_value TYPE i RETURNING VALUE(rv_value) TYPE i. ENDCLASS. CLASS lcl_app IMPLEMENTATION. METHOD change_value. cv_value = cv_value + 1. rv_value = cv_value. ENDMETHOD. METHOD change_data. DATA(lt_data) = VALUE tt_data( FOR <ls> IN CAST lcl_app( get_reference( ) )->mt_data ( change_value( CHANGING cv_value = <ls> ) ) ). ENDMETHOD. METHOD get_reference. ro_data = me. ENDMETHOD. ENDCLASS. START-OF-SELECTION. DATA(lo_app) = NEW lcl_app( ). lo_app->mt_data = VALUE #( ( 1 ) ). lo_app->change_data( ). cl_demo_output=>display( lo_app->mt_data ). |
С помощью CAST мы таки смогли изменить данные. Пример сделан исключительно ради демонстрации, не стоит создавать методы вроде change_value с изменением переменной и одновременным возвратом 🙂
P.S. Обратите внимание, если используете временные таблицы в циклах, после завершения цикла, обратиться к FIELD-SYMBOLS будет так же невозможно, т.к. присвоение пропадёт вместе в временной таблицей:
1 2 3 4 5 6 |
... LOOP AT get_reference( )->mt_data ASSIGNING FIELD-SYMBOL(<lv_value>). <lv_value> = <lv_value> + 1. ENDLOOP. <lv_value> = 10. " <-- Dump here ... |
Дополнительный пример с неочевидным поведением, где происходит принудительная передача параметра по значению:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
CLASS lcl_app DEFINITION. PUBLIC SECTION. METHODS: get_me RETURNING VALUE(ro_me) TYPE REF TO lcl_app, get_reference IMPORTING iv_string TYPE string RETURNING VALUE(rr_ref) TYPE REF TO string, constructor IMPORTING iv_app_name TYPE string. DATA: mv_app_name TYPE string. ENDCLASS. CLASS lcl_app IMPLEMENTATION. METHOD constructor. mv_app_name = iv_app_name. ENDMETHOD. METHOD get_me. ro_me = me. ENDMETHOD. METHOD get_reference. rr_ref = REF #( iv_string ). ENDMETHOD. ENDCLASS. START-OF-SELECTION. DATA(lo_app) = NEW lcl_app( 'Application' ). DATA(lr_ref) = lo_app->get_reference( lo_app->get_me( )->mv_app_name ). ASSIGN lr_ref->* TO FIELD-SYMBOL(<lv_app_name>). cl_demo_output=>write( data = lo_app->mv_app_name ). cl_demo_output=>display( <lv_app_name> ). |
Passing Parameters
When binding a function, a calculation expression, a constructor expression, or a table expression, the parameters are always passed by value, even if the formal parameter is defined as to be passed by reference.
Но как передать table expression по значению, я так и не понял. Возможно ошибка в документации, либо я её неверно истолковал. Если есть идеи на данный счёт просьба прокомментировать.