pascalabcnet/bin/Lib/BlockFileOfT.pas

783 lines
32 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(************************************************************************************)
// Copyright (©) Cergey Latchenko ( github.com/SunSerega | forum.mmcs.sfedu.ru/u/sun_serega )
// This code is distributed under the Unlicense
// For details please see LICENSE.md or here:
// https://github.com/SunSerega/PascalABC.Net-BlockFileOfT/blob/master/LICENSE.md
(************************************************************************************)
// Copyright (©) Сергей латченко ( github.com/SunSerega | forum.mmcs.sfedu.ru/u/sun_serega )
// Этот код распространяется под Unlicense
// Для деталей смотрите в файл LICENSE.md или сюда:
// https://github.com/SunSerega/PascalABC.Net-BlockFileOfT/blob/master/LICENSE.md
(************************************************************************************)
///Модуль содержащий тип BlockFileOf<T>
///Этот тип - альтернатива стандартному file of T
///Основное преимущество - скорость работы
unit BlockFileOfT;
interface
uses System.Runtime.InteropServices;
uses System.IO;
type
///--
BlockFileBase = abstract class
protected fi:FileInfo;
protected _offset:int64;
protected str:FileStream;
protected bw:BinaryWriter;
protected br:BinaryReader;
protected linked := new List<BlockFileBase>;
protected procedure Link(f:BlockFileBase);
protected procedure UnLink;
private class function MessageBox(wnd: System.IntPtr; message, caption: string; flags: cardinal):integer;
external 'User32.dll';
end;
///Тип, записывающий данные в файл по принципу схожему с "file of <T>"
///Но в отличии от "file of <T>" - данный тип сохраняет всю запись одним блоком,
///так, как она записа в памяти.
///Это даёт значительное преимущество по скорости, но ограничевает,
///типы, которые могут быть использованы в виде полей <T>
///
///Ожидается, что в видет шаблонного параметра <T> будет передана запись,
///не содержащая динамичных полей.
///Иначе целостность данных будет терятся
///Это значит, что поля записи T и всех вложенных записей - НЕ могут быть:
/// -Указатели
/// -Ссылочных типов (то есть классами)
/// -Особых типов, которые .Net считает "опасными". Как char или System.DateTime
///Но эти ограничения можно обойти, про это в справке
BlockFileOf<T>=class(BlockFileBase)
private class sz: integer;
private class procedure TestForRefT(tt: System.Type);
begin
if tt.IsClass then
begin
MessageBox(new System.IntPtr(nil),
$'Тип {tt} ссылочный.{#10}Ссылочные типы нельзя сохранять в типизированный файл.{#10}Нажмите OK для выхода.',
$'Тип T из BlockFileOf<T> содержет ссылочные типы',
$10
);
Halt(-1);
end;
foreach var fi in
tt.GetFields(
System.Reflection.BindingFlags.GetField or
System.Reflection.BindingFlags.Instance or
System.Reflection.BindingFlags.Public or
System.Reflection.BindingFlags.NonPublic
) do
if not fi.IsLiteral then
if fi.FieldType <> tt then//integer имеет поле типа integer, без этой строчки StackOwerflow
TestForRefT(fi.FieldType);
end;
private class constructor :=
try
TestForRefT(typeof(T));
(*
try
var a := new T[0];
GCHandle.Alloc(a,GCHandleType.Pinned).Free;
except
on e: System.ArgumentException do
begin
MessageBox(new System.IntPtr(nil),
$'.Net не принимает какой то из типов полей записи, для которой вы создали BlockFileOf<T>{#10}Говорит - его нельзя превратить в набор байт{#10}Нажмите OK для выхода.',
$'Тип T содержет ссылочные типы',
$10
);
Halt(-1);
end;
end;
(**)
sz := Marshal.SizeOf(typeof(T));
except
on e:Exception do
begin
MessageBox(new System.IntPtr(nil),
e.ToString,
$'При инициализации BlockFileOf<{typeof(T)}> произошла ошибка',
$10
);
Halt(-1);
end;
end;
private function GetName:string;
private function GetFullName:string;
private function GetExists:boolean;
private function GetFileSize:int64 := (GetByteFileSize - _offset) div sz;
private procedure SetFileSize(size:int64) := SetByteFileSize(_offset + size*sz);
private function GetByteFileSize:int64;
private procedure SetByteFileSize(size:int64);
private function GetPos:int64 := (GetPosByte-_offset) div sz;
private procedure SetPos(pos:int64) := SetPosByte(_offset + pos*sz);
private function GetPosByte:int64;
private procedure SetPosByte(pos:int64);
private function InternalReadLazy(c:integer; start_pos:int64):sequence of T;
///Инициализирует переменную файла, не привязывая её к файлу на диске
public constructor := exit;
///Инициализирует переменную файла, привязывая её к файлу fname
public constructor(fname:string) :=
Assign(fname);
///Инициализирует переменную файла, привязывая её к файлу fname
///А так же устанавливая значение смещени от начала в байтах на offset
public constructor(fname:string; offset:int64) :=
Assign(fname, offset);
///- constructor BlockFileOf<>(f:BlockFileOf<>);
///Инициализирует новую переменную, создавая связку данной переменной
///После вызова этого конструктора - переданная и созданная переменная будут
///использовать общий файловый поток, но записывать разные типы данных (у них может быть разный T)
///Это значит, что переменная которую передали в конструктор должно уже иметь открытый файловый поток
///Метод Close разрывает эту связь.
public constructor(f: BlockFileBase) :=
Link(f);
///Размер блока из одного элемента типа T, в байтах
///Хоть это свойство и можно перезаписывать, но это не рекомендуется
///Если перезаписать на число, большее чем было - в файл будет сохранять лишние нули
///А если на меньшее - получите неопределённое поведение (скорее всего вылет, но далеко не сразу)
public property TSize:integer read integer(sz) write sz := value;
///Смещение от начала файла до начала элементов (в байтах)
public property Offset:int64 read _offset write _offset;
///Количество сохранённых в файл элементов типа T
///Чтоб установить длину файла - надо открыть файл. Но прочитать длину можно не открывая
public property Size:int64 read GetFileSize write SetFileSize;
///Полный размер файла, в байтах
///Чтоб установить длину файла - надо открыть файл. Но прочитать длину можно не открывая
public property ByteSize:int64 read GetByteFileSize write SetByteFileSize;
///Имя файла (только имя самого файла, без имени папки)
public property Name:string read GetName;
///Полное имя файла (вместе с именами всех под-папок, вплодь до корня диска)
public property FullName:string read GetFullName;
///Существует ли файл
public property Exists:boolean read GetExists;
///Номер текущего элемета типа T в файле (нумеруя с 0)
public property Pos:int64 read GetPos write SetPos;
///Номер текущего байта от начала файла (нумеруя с 0)
public property PosByte:int64 read GetPosByte write SetPosByte;
///Привязана ли переменная к файлу
public property Assigned:boolean read fi <> nil;
///Открыт ли файл
public property Opened:boolean read str <> nil;
///Достигнут ли конец файла
public property EOF:boolean read ByteSize-PosByte < sz;
///Основной поток открытого файла (или nil если файл не открыт)
///Внимание! Любое действие которое изменит этот поток - приведёт к неожиданным последствиям, используйте его только если знаете что делаете
public property BaseStream:FileStream read str;
///Переменная, которая записывает данные в основной поток (или nil если файл не открыт)
///Внимание! Любое действие которое изменит основной поток файла - приведёт к неожиданным последствиям, используйте его только если знаете что делаете
public property BinWriter:BinaryWriter read bw;
///Переменная, которая читает данные из основного потока (или nil если файл не открыт)
///Внимание! Любое действие которое изменит основной поток файла - приведёт к неожиданным последствиям, используйте его только если знаете что делаете
public property BinReader:BinaryReader read br;
///Переменная, показывающая данные о файле (или nil, если переменная не привязана к файлу)
public property FileInfo:System.IO.FileInfo read fi;
///Привязывает данную переменную к файлу {fname}
///Привязывать можно и к не существующим файлам, при откритии определёнными способами (как Rewrite) новый файл будет создан
public procedure Assign(fname:string);
///Привязывает данную переменную к файлу {fname}
///А так же устанавливая значение смещени от начала в байтах на offset
///Привязывать можно и к не существующим файлам, при откритии определёнными способами (как Rewrite) новый файл будет создан
public procedure Assign(fname:string; offset:int64);
///Убирает свять переменной и файла, если связь есть
public procedure UnAssign;
///Открывает файл, способом описанным в переменной mode
///Чтоб получить переменную этого типа - пишите System.IO.FileMode.<способ_открытия_файла>
public procedure Open(mode:FileMode);
///Удаляет связаный файл, если он существует
public procedure Delete;
///Переименовывает файл
///Если указать другое расположение - файл будет перемещён
public procedure Rename(NewName:string);
///Создает (или обнуляет) привязаный файл
public procedure Rewrite;
///Привязывает данную переменную к файлу {fname} и создает (или обнуляет) этот файл
public procedure Rewrite(fname:string);
///Привязывает данную переменную к файлу {fname} и создает (или обнуляет) этот файл
///А так же устанавливая значение смещени от начала в байтах на offset
public procedure Rewrite(fname:string; offset:int64);
///Открывает файл (ожидается, что он уже существует) и устанавливает позицию на начало файла
public procedure Reset;
///Привязывает данную переменную к файлу {fname}, открывает этот файл (ожидается, что файл уже существует) и устанавливает позицию на начало файла
public procedure Reset(fname:string);
///Привязывает данную переменную к файлу {fname}, открывает этот файл (ожидается, что файл уже существует) и устанавливает позицию на начало файла
///А так же устанавливая значение смещени от начала в байтах на offset
public procedure Reset(fname:string; offset:int64);
///Открывает файл (ожидается, что он уже существует) и устанавливает позицию в конце файла
public procedure Append;
///Привязывает данную переменную к файлу {fname}, открывает этот файл (ожидается, что файл уже существует) и устанавливает позицию в конце файла
public procedure Append(fname:string);
///Привязывает данную переменную к файлу {fname}, открывает этот файл (ожидается, что файл уже существует) и устанавливает позицию в конце файла
///А так же устанавливая значение смещени от начала в байтах на offset
public procedure Append(fname:string; offset:int64);
///Переставляет позицию в файле на элемент #pos (нумеруя с 0)
public procedure Seek(pos:int64) := self.Pos := pos;
///Переставляет файловый курсор на байт #pos (нумеруя с 0) от начала файла
public procedure SeekByte(pos:int64) := self.PosByte := pos;
///Записывает все изменения в файл и отчищает внутренние буферы
///До вызова Flush или Close - все изменения и кеш хранятся в оперативной памяти
///Поэтому, если вы записываете/читаете много (сотни мегабайт) - лучше вызывать Flush время от времени
public procedure Flush;
///Сохраняет и закрывает файл, если он открыт
public procedure Close;
///Записывает один элемент одним блоком в файл
///И переставляет файловый курсор на 1 элемет вперёд
public procedure Write(o: T);
///Записывает массив элементов одним блоком в файл
///И переставляет файловый курсор на o.Length элеметов вперёд
public procedure Write(params o:array of T);
///Записывает последовательность элементов, у которой можно узнать длину, одним блоком в файл
///И переставляет файловый курсор на o.Count элеметов вперёд
public procedure Write(o:ICollection<T>);
///Записывает последовательность элементов, у которой нельзя узнать длину, по 1 элементу типа T в файл
///И переставляет файловый курсор на o.Count элеметов вперёд
public procedure Write(o:sequence of T);
///Записывает count элементов массива, начиная с элемента #from, одним блоком в файл
///И переставляет файловый курсор на сount элеметов вперёд
public procedure Write(o:array of T; from,count:integer);
///Записывает count элементов последовательности, у которой можно узнать длину, начиная с элемента #from, одним блоком в файл
///И переставляет файловый курсор на сount элеметов вперёд
public procedure Write(o:ICollection<T>; from,count:integer);
///Записывает count элементов последовательности, у которой нельзя узнать длину, начиная с элемента #from, одним блоком в файл
///И после каждого элемента переставляет файловый курсор на 1 элемет вперёд
public procedure Write(o:sequence of T; from,count:integer);
///Читает один элемент из файла одним блоком
///И переставляет файловый курсор на 1 элемет вперёд
public function Read:T;
///Читает массив из count одним блоком
///И переставляет файловый курсор на count элеметов вперёд
public function Read(count:integer):array of T;
///Читает массив из count элементов одним блоком, начиная с элемента #start_elm
///И переставляет файловый курсор на count элеметов вперёд
public function Read(start_elm, count:integer):array of T;
///Возвращает ленивую последовательность из count элементов
///После завершения чтения - курсор окажется на эдлементе #(start_elm+count)
///Каждый раз читать будет начиная с элемента, на котором сейчас стоит файловый курсор
public function ReadLazy(count:integer):sequence of T := InternalReadLazy(count, PosByte);
///Возвращает ленивую последовательность из count элементов, начиная с элемента #start_elm
///После прочтения i элементов последовательности - позиция в файле будет передвигаться на элемент #(start_elm+i)
///Каждый раз читать будет начиная с элемента #start_elm
public function ReadLazy(start_elm, count:integer):sequence of T := InternalReadLazy(count, _offset + start_elm*sz);
///Возвращает ленивую последовательность из блоков-массивов с элементами типа T
///Каждый блок хранит столько элементов - чтоб не превышать объём в 4096 байт (4КБ)
///После прочтения каждого блока - файловый корсор будет переставлен на его конец
public function ToSeqBlocks:sequence of array of T := ToSeqBlocks(4096);
///Возвращает ленивую последовательность из блоков-массивов с элементами типа T
///Каждый блок хранит столько элементов - чтоб не превышать объём в последовательность байт
///После прочтения каждого блока - файловый корсор будет переставлен на его конец
public function ToSeqBlocks(blocks_size:integer):sequence of array of T;
///Возвращает ленивую последовательность из всех элементов хранящихся в файле
///После прочтения i элементов последовательности - позиция в файле будет передвигаться на элемент #i
public function ToSeq:sequence of T;
protected procedure Finalize; override;
begin
Close;
end;
end;
{$region Exception's}
type
FileNotAssignedException = class(Exception)
constructor :=
inherited Create($'Данная переменная не была привязана к файлу{10}Используйте метод Assign');
end;
FileNotOpenedException = class(Exception)
constructor(fname:string) :=
inherited Create($'Файл {fname} ещё не открыт, откройте его с помощью Open, Reset, Append или Rewrite');
end;
FileNotClosedException = class(Exception)
constructor(fname:string) :=
inherited Create($'Файл {fname} ещё открыт, закройте его методом Close перед тем как продолжать');
end;
CannotReadAfterEOF = class(Exception)
constructor :=
inherited Create($'Нельзя читать за пределами файла. Можно только записывать');
end;
{$endregion Exception's}
implementation
{$region Linking}
procedure BlockFileBase.Link(f:BlockFileBase);
begin
if f.str = nil then raise new FileNotOpenedException($'{f.fi.FullName}, чью переменную передали в конструктор BlockFileOf<T>,');
foreach var l in f.linked + f do
begin
self.linked.Add(l);
l.linked.Add(self);
end;
self.str := f.str;
self.br := f.br;
self.bw := f.bw;
self.fi := new FileInfo(f.fi.FullName);
end;
procedure BlockFileBase.UnLink;
begin
foreach var l in self.linked do
l.linked.Remove(self);
self.linked.Clear;
end;
{$endregion Linking}
{$region property implementation}
function BlockFileOf<T>.GetName:string;
begin
if fi = nil then raise new FileNotAssignedException;
fi.Refresh;
Result := fi.Name;
end;
function BlockFileOf<T>.GetFullName:string;
begin
if fi = nil then raise new FileNotAssignedException;
//fi.Refresh;//А тут не надо
Result := fi.FullName;
end;
function BlockFileOf<T>.GetByteFileSize:int64;
begin
if fi = nil then raise new FileNotAssignedException;
if str <> nil then
begin
Result := str.Length;
exit;
end;
fi.Refresh;
Result := fi.Length;
end;
procedure BlockFileOf<T>.SetByteFileSize(size:int64);
begin
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
str.SetLength(size);
end;
function BlockFileOf<T>.GetPosByte:int64;
begin
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
Result := str.Position;
end;
procedure BlockFileOf<T>.SetPosByte(pos:int64);
begin
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
str.Position := pos;
end;
{$endregion property implementation}
{$region Setup IO}
{$region Basic}
procedure BlockFileOf<T>.Assign(fname:string);
begin
if str <> nil then raise new FileNotClosedException(fi.FullName);
fi := new System.IO.FileInfo(fname);
end;
procedure BlockFileOf<T>.Assign(fname:string; offset:int64);
begin
Assign(fname);
_offset := offset;
end;
procedure BlockFileOf<T>.UnAssign;
begin
if str <> nil then raise new FileNotClosedException(fi.FullName);
fi := nil;
end;
procedure BlockFileOf<T>.Open(mode:FileMode);
begin
if fi = nil then raise new FileNotAssignedException;
if str <> nil then raise new FileNotClosedException(fi.FullName);
str := fi.Open(mode);
bw := new BinaryWriter(str);
br := new BinaryReader(str);
end;
procedure BlockFileOf<T>.Delete;
begin
if fi = nil then raise new FileNotAssignedException;
fi.Delete;
end;
function BlockFileOf<T>.GetExists:boolean;
begin
if fi = nil then raise new FileNotAssignedException;
fi.Refresh;
Result := fi.Exists;
end;
procedure BlockFileOf<T>.Rename(NewName:string);
begin
if fi = nil then raise new FileNotAssignedException;
if str <> nil then raise new FileNotClosedException(fi.FullName);
fi.MoveTo(NewName);
end;
{$endregion Assign}
{$region Rewrite}
procedure BlockFileOf<T>.Rewrite :=
Open(FileMode.Create);
procedure BlockFileOf<T>.Rewrite(fname:string);
begin
Assign(fname);
Rewrite;
end;
procedure BlockFileOf<T>.Rewrite(fname:string; offset:int64);
begin
Assign(fname, offset);
Rewrite;
end;
{$endregion Rewrite}
{$region Reset}
procedure BlockFileOf<T>.Reset :=
Open(FileMode.Open);
procedure BlockFileOf<T>.Reset(fname:string);
begin
Assign(fname);
Reset;
end;
procedure BlockFileOf<T>.Reset(fname:string; offset:int64);
begin
Assign(fname, offset);
Reset;
end;
{$endregion Reset}
{$region Append}
procedure BlockFileOf<T>.Append :=
Open(FileMode.Append);
procedure BlockFileOf<T>.Append(fname:string);
begin
Assign(fname);
Append;
end;
procedure BlockFileOf<T>.Append(fname:string; offset:int64);
begin
Assign(fname, offset);
Append;
end;
{$endregion Append}
{$region Closing}
procedure BlockFileOf<T>.Flush;
begin
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
str.Flush;
end;
procedure BlockFileOf<T>.Close;
begin
if str <> nil then
begin
if linked.Count <> 0 then
UnLink else
str.Close;
str := nil;
br := nil;
bw := nil;
end;
end;
{$endregion Closing}
{$endregion Setup IO}
{$region Write}
procedure BlockFileOf<T>.Write(o: T);
begin
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
var a := new byte[sz];
var gc_hnd := GCHandle.Alloc(a, GCHandleType.Pinned);
System.Buffer.MemoryCopy(
@o,
gc_hnd.AddrOfPinnedObject.ToPointer,
sz,sz
);
gc_hnd.Free;
bw.Write(a);
end;
procedure BlockFileOf<T>.Write(params o:array of T);
begin
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
var bl := sz*o.Length;
var a := new byte[bl];
var gc_hnd1 := GCHandle.Alloc(o, GCHandleType.Pinned);
var gc_hnd2 := GCHandle.Alloc(a, GCHandleType.Pinned);
System.Buffer.MemoryCopy(
gc_hnd1.AddrOfPinnedObject.ToPointer,
gc_hnd2.AddrOfPinnedObject.ToPointer,
bl,bl
);
gc_hnd1.Free;
gc_hnd2.Free;
bw.Write(a);
end;
procedure BlockFileOf<T>.Write(o:ICollection<T>);
type TArr = array of T;
begin
if o is TArr(var a) then
begin
Write(a);
exit;
end;
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
var a := new byte[sz*o.Count];
var gc_hnd := GCHandle.Alloc(a, GCHandleType.Pinned);
var hnd := gc_hnd.AddrOfPinnedObject;
foreach var el in o do
begin
System.Buffer.MemoryCopy(
@el,
hnd.ToPointer,
sz,sz
);
System.IntPtr.Add(hnd, sz);
end;
gc_hnd.Free;
bw.Write(a);
end;
procedure BlockFileOf<T>.Write(o:sequence of T) :=
if o is ICollection<T>(var c) then
Write(c) else
foreach var el in o do
Write(el);
procedure BlockFileOf<T>.Write(o:array of T; from,count:integer);
begin
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
var bl := sz*count;
var a := new byte[bl];
var gc_hnd1 := GCHandle.Alloc(o, GCHandleType.Pinned);
var gc_hnd2 := GCHandle.Alloc(a, GCHandleType.Pinned);
System.Buffer.MemoryCopy(
System.IntPtr.Add(gc_hnd1.AddrOfPinnedObject, from * sz).ToPointer,
gc_hnd2.AddrOfPinnedObject.ToPointer,
bl,bl
);
gc_hnd1.Free;
gc_hnd2.Free;
bw.Write(a);
end;
procedure BlockFileOf<T>.Write(o:ICollection<T>; from,count:integer);
type TArr = array of T;
begin
if o is TArr(var a) then
begin
Write(a, from, count);
exit;
end;
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
var a := new byte[sz*o.Count];
var gc_hnd := GCHandle.Alloc(a, GCHandleType.Pinned);
var hnd := gc_hnd.AddrOfPinnedObject;
foreach var el in o do
if from > 0 then from -= 1 else
if count > 0 then
begin
System.Buffer.MemoryCopy(
@el,
hnd.ToPointer,
sz,sz
);
hnd := System.IntPtr.Add(hnd, sz);
count -= 1;
end;
gc_hnd.Free;
bw.Write(a);
end;
procedure BlockFileOf<T>.Write(o:sequence of T; from,count:integer) :=
if o is ICollection<T>(var c) then
Write(c,from,count) else
Write(o.Skip(from).Take(count));
{$endregion Write}
{$region Read}
function BlockFileOf<T>.Read:T;
begin
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
if str.Length - str.Position < sz then raise new CannotReadAfterEOF;
var a := br.ReadBytes(sz);
var gc_hnd := GCHandle.Alloc(a, GCHandleType.Pinned);
System.Buffer.MemoryCopy(
gc_hnd.AddrOfPinnedObject.ToPointer,
@Result,
sz,sz
);
gc_hnd.Free;
end;
function BlockFileOf<T>.Read(count:integer):array of T;
begin
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
if str.Length - str.Position < sz*count then raise new CannotReadAfterEOF;
var bl := sz*count;
var a := br.ReadBytes(bl);
Result := new T[count];
var gc_hnd1 := GCHandle.Alloc(a, GCHandleType.Pinned);
var gc_hnd2 := GCHandle.Alloc(Result, GCHandleType.Pinned);
System.Buffer.MemoryCopy(
gc_hnd1.AddrOfPinnedObject.ToPointer,
gc_hnd2.AddrOfPinnedObject.ToPointer,
bl,bl
);
gc_hnd1.Free;
gc_hnd2.Free;
end;
function BlockFileOf<T>.Read(start_elm, count:integer):array of T;
begin
Pos := start_elm;
Result := Read(count);
end;
function BlockFileOf<T>.InternalReadLazy(c:integer; start_pos:int64):sequence of T;
begin
if fi = nil then raise new FileNotAssignedException;
if str = nil then raise new FileNotOpenedException(fi.FullName);
if str.Length - start_pos < sz*c then raise new CannotReadAfterEOF;
for var i := 0 to c-1 do
begin
PosByte := start_pos + i*sz;
yield Read;
end;
end;
function BlockFileOf<T>.ToSeqBlocks(blocks_size:integer):sequence of array of T;
begin
var c := blocks_size div sz;
var i := 0;
while true do
begin
var left := Size - i;
if left < c then
begin
if left > 0 then
begin
Pos := i;
yield Read(left);
end;
exit;
end else
begin
Pos := i;
yield Read(c);
i += c;
end;
end;
end;
function BlockFileOf<T>.ToSeq:sequence of T;
begin
var i := 0;
while not EOF do
begin
Pos := i;
yield Read;
i += 1;
end;
end;
{$endregion Read}
end.