<=
Сохранение ссылочных типов в блочный файл.

В чём проблема ссылочных типов

type
r1 = record
b: byte;
i: integer;
s: string;
end;
begin
var a: r1;
a.b := 5;
a.i := 12345;
a.s := 'abcdef'*10000;//Это 60 000 символов, ~120KB. А значит в записи никаким образом не поместилось бы
end.

r1, byte и integer из этого примера - размерные типы (записи), а значит они хранят свои данные прямо в переменных.
А string - ссылочный тип (класс). Это значит что в поле r1.s хранится только ссылка (особый указатель) на содержимое строки.
То есть, в переменной "a" хранится три значения: byte, integer и ссылка.
А содержимое строки хранится где-то в оперативной памяти, куда указывает эта ссылка.
"BlockFileOf<T>" берёт содержимое переменных одним блоком памяти.
Поэтому, если попытаться сохранить содержимое r1 в типизированный файл.
(отключив защиту от дурака, запрещающую ссылочные типы)
В файл сохранится значение ссылки, а не строки.
Кроме того, ссылки отличаются от указателей тем, что они ссылаются на значение, которое может "плавать" в оперативной памяти.
Точнее, сборщик мусора может двигать эти значения, для экономии места.
Это значит что если сохранить запись с ссылкой в файл и сразу загрузить эту ссылку назад.
Её значение может быть уже устаревшим.

Размерные массивы и строки

Посмотрите в этот файл:
C:\PABCWork.NET\Samples\BlockFileOfT\Сравнение скорости.pas
Найдите там тип "ValueString255".
На нём будут строиться дальнейшие обноснования:
Этот тип представляет строку с фиксированной длинной, а точнее длиной в 255 символов.
Первое, что видно - прямо перед объявлением типа стоит атрибут "StructLayout":
"LayoutKind.Explicit" разрешает применять атрибут "FieldOffset" к полям.
А "Size" позволяет явно указать сколько байт выделять под данную запись.
Благодаря последнему - можно выделить место сразу под все 255 символов строки, не делая 255 полей с типом "char".
Далее, в записи "ValueString255" есть всего 2 поля, "length" (длина) и "body" (тело, а точнее его начало)
"length" это кол-во символов во всей строке. В данном случае не может быть больше 255 символов,
поэтому достаточно 1 байта чтобы хранить любую возможную длину строки.
А "body" это начало самогО содержимого строки.
Вообще, поле "body" не так уж и нужно, но с ним будет проще, потому что если создать указатель на "body" -
он сразу оказывается и указателем на содержимое строки, без каких-либо преобразований.
Далее идут методы для преобразования между обычной строкой и нашей размерной.
Я сделал их "operator explicit"-ами, потому что их вызов красивый и удобный.
Смотрим в первый, преобразовывающий массив символов в размерную строку:
Если выполнить "Result.length := a.Length" и передать массив длинной больше 255 символов -
в этой строчке произойдёт переполнение и "Result.length" запонится мусором, поэтому сначала надо уменьшить значение до "MaxLength".
Далее, если длина равна нулю - ничего больше делать не надо.
В противном случае - используем процедуру "CopyMem".
Насчёт того, как работает "CopyMem", можно написать целую диссертацию.
Но главное, что надо знать вам: если попытаться получить указатель на элемент массива ("@a[n]") -
получаем утечку памяти. Однако, если передать "a[n]" в "CopyMem" как var-параметр
(что, по сути, тоже создаёт указатель на этот элемент массива),
то всё в порядке.
В данном случае мы копируем память начиная с нулевого элемента массива
Вставляем её в "Result.body"
И всего копируем "Result.length*2" байт (*2 потому что каждый символ занимает 2 байта в памяти)
Преобразование из "string" в размерную строку работает также, там ничего интересного.
У преобразования размерной строки в массив символов всего одна особенность:
Теперь результат у нас ссылочный (массив), поэтому память под него нужно выделить вручную ("new char[...]").
И последнее преобразование, из "ValueString255" в "string":
А вот тут очень приятно вышло - у "string" есть конструктор который идеально подходит:
Первый параметр это указатель на начало блока памяти содержащего символы. То есть "@s.body" и даже тип подходит без преобразований.
Второй параметр - смещение начала. У нас символы начинаются прямо на "body", поэтому 0.
И последний - количество символов.
Последнее, что есть в данной записи - перегрузка "ToString". Это вообще не обязательный код,
но с этой функцией размерная строка будет вести себя точно так же, как и обычная, если передать её во "Writeln".
Также можно добавлять и другие свои методы в данный тип и даже сделать все те же методы, как и у ссылочных строк, или даже больше.
Ну а размерные массивы ничем не отличаются в реализации.
Можно даже сказать, что "ValueString255" это размерный массив символов, ничего от этого не изменится.