Решение более-менее серьезных задач обычно требует длительного хранения информации — настроек программы, истории работы и т. д. Этим объясняется постоянный интерес к теме работы с файлами. Платформа .NET имеет все необходимое для того, чтобы программист забыл про существование устройств хранения данных и полностью доверился «матрице» — мощной объектной модели.

Получив такой инструмент, как Delphi Prism, многие «дельфисты» обрели полноценный доступ к технологиям самой последней версии платформы .NET. Конечно, если сравнивать новый продукт, например с C#, наверняка в нем пока будет чего-то не хватать, однако сам факт выхода Delphi Prism, кажется, решает проблему постоянного отставания Delphi.NET от поддержки широкого набора средств работы под .NET и Mono.

В настоящее время все тонкости работы в новой среде программирования можно условно разделить на относящиеся к употреблению пока еще непривычного для большинства «дельфистов» синтаксиса и на касающиеся свежих возможностей самой платформы .NET.

Особенности платформы .NET

Когда речь заходит о работе с файлами на платформе .NET, то на первое место сразу же выступает тема использования асинхронных потоков данных, точнее, даже не самих потоков, а классов-оберток, которые обеспечивают программисту рутинные операции.

Отдельно стоит упомянуть класс File (System.IO.File). В нем собраны многие возможности по работе с файловой структурой. Для его использования требуется .NET Framework 3.5 — современная версия .NET, поддерживаемая Delphi Prism. С нее-то мы и начнем.

Сначала в первом примере просто создадим новый файл с помощью волшебного класса File.

Листинг 1. Чтение текстового файла с использованием классов File и StreamWriter

namespace Ex1;
interface
uses System.IO;
type 
ConsoleApp = class 
public 
class method Main;
end;
implementation 
class method ConsoleApp.Main;
begin
// Создание текстового файла и запись в него данных
var filename:=”C:Ex1.txt”; 
using tr:StreamWriter:=File.CreateText(filename) do begin 
tr.WriteLine(”Насколько схожи  традиционный Delphi и Prism?”); 
tr.WriteLine(” Первое и важнейшее  их различие”); 
tr.WriteLine(” — поддержка в Prism современной”); 
tr.WriteLine(” актуальной версии платформы .NET”); 
end;
//Вывод в консоль содержимого файла вместе с датой создания Чем-то похоже на FileToString из JEDI для //Delphi 
Console.WriteLine(File.ReadAllText(filename)); 
Console.WriteLine((new FileInfo(filename)).CreationTime.ToLongDateString); 
File.Delete(filename); 
Console.ReadKey;
end;
end.

Класс StreamWriter предназначен специально для записи текстового файла. При этом мы почти до предела упростили для себя работу, используя конструкцию using … do begin … end; Она заменяет конструкцию

try
finally 
if (_Object is Idisposable) then (_Object as Disposable).Dispose();
end;

где _Object — условное обозначение создаваемого объекта. Таким образом, программист частично снимает с себя ответственность за жизненный цикл созданного объекта независимо от модели его памяти. Напоследок упомянем FileInfo — еще один волшебный класс, сопоставимый по мощности с классом File. Оба они предназначены для решения одних и тех же задач, но для FileInfo нужно создавать экземпляр класса.

StreamWriter и StreamReader — классы, специально созданные для работы с текстом. Конечно, желательно, чтобы вы были уверены, что будете работать именно с текстом, в противном случае рекомендуется выбрать класс FileStream. Классы StreamWriter и StreamReader освобождают программиста от забот, связанных с кодировкой. Поразительно, насколько все кажется в .NET взаимозаменяемым и универсальным!

Теперь напишем еще более скромную программу для чтения файла EMPLOYEE.txt, использовав класс StreamReader. Сначала экспортируем таблицу EMPLOYEE.FDB (из демонстрационной базы для пакета FireBird 2.0) в текстовый файл. В качестве разделителя зададим символ «;». В результате получим полноценный CSV-файл, с которым можно упражняться.

Листинг 2. Чтение текстового файла EMPLOYEE.txt с использованием класса StreamReader

class method ConsoleApp.Main;
begin 
//Для чтения текстового файла применим класс StreamReader 
using tr: StreamReader:=new StreamReader(”C:EMPLOYEE.txt”) do begin 
loop begin 
var line:=tr.ReadLine ;
if not(Assigned(line)) then break; 
Console.WriteLine(line); 
end; 
Console.ReadLine; 
end; 
end;
end.
 

Рассмотрим второй пример. Вместо строки

tr:StreamReader:=new StreamReader(”C:EMPLOYEE.txt”)

можно просто написать

tr:=new StreamReader(”C:EMPLOYEE.txt”)

Полученная конструкция также будет работать. Обратите внимание: если вместо конструктора StreamReader поставить родительский конструктор TextReader и явно указать тип StreamReader для идентификатора tr, то компилятор и это верно расценит.

Простым чтением собственного файла никого не удивить, поэтому добавим элементы анализа данных, т. е. поставим задачу разбора полей.

Листинг 3. Применение технологии OLEDB для чтения и анализа данных текстового файла

class method ConsoleApp.Main;
begin //Читаем текстовый файл с использованием технологии OLEDB 
var FileName:=’C:EMPLOYEE.txt’;
var Delimited:=’;’;
var file:FileInfo:=new FileInfo(FileName); 
using  con:OleDbConnection:= new OleDbConnection(‘Provider=Microsoft.Jet.OLEDB.4.0;Data Source=’+file.DirectoryName+‘;Extended Properties=’’text;HDR=Yes;FMT=Delimited(‘+Delimited +’)’’;’) do begin con.Open(); 
using cmd:OleDbCommand:=new OleDbCommand(string.Format (”SELECT * FROM [{0}]”, File.Name), con) do begin
using rdr:OleDbDataReader:=cmd.ExecuteReader() do begin
while (rdr.Read()) do begin //Выборочно выводим столбцы в консоль
Console.WriteLine(‘Имя:{0} Фамилия:{1} Отдел:{2}’,rdr[1],rdr[2],rdr[5]);
end;
rdr.Close;
end;
Console.ReadLine;
end;
end;
end;
end.

В третьем примере была использована технология работы с базами данных без самой базы. Конечно, можно решать задачу в лоб, используя основные возможности платформы .NET, — применить метод класса String.Split, который автоматически будет разбивать очередную читаемую строку на элементы и заносить их в массив, но, согласитесь, что вышло оригинально.

И все это можно делать в Delphi Prism только потому, что реализован полноценный доступ к богатству средств .NET. Аналогичный примененному в  нашем примере текст может быть элементом хитрого алгоритма для обработки разнородных данных из различных источников.

Теперь перейдем к классу TextFieldParser, изначально созданному для программистов, работающих на Бейсике. Для доступа к этому классу надо добавить в программу ссылку на Microsoft.VisualBasic.FileIO (Microsoft.VisualBasic.dll).

Листинг 4. Чтение текстового файла и запись его данных в бинарном формате с помощью сериализации

...

[Serializable]
TEMPLOYEE = public class
public
EMP_NO:SmallInt;
HIRE_DATE:DateTime;
JOB_GRADE:SmallInt;
SALARY:Decimal;
FIRST_NAME:String;
LAST_NAME:String;
PHONE_EXT:String;
DEPT_NO:String;
JOB_CODE:String;
JOB_COUNTRY:String;
FULL_NAME:String;
end;
implementation //В этот раз для чтения текстового файла используем класс TextFieldParser и сериализуем данные — в этом случае записываем их на диск в бинарном формате.

class method ConsoleApp.Main;
var EMP:TEMPLOYEE;
win1251:=Encoding.GetEncoding(”windows-1251”);
EMPS:=new ArrayList();
begin
File.Delete(‘C:EMPLOYEE.dat’);
using parser:TextFieldParser:=new
TextFieldParser(‘C:EMPLOYEE.txt’,win1251) do begin
parser.SetDelimiters(”;”);
while not(parser.EndOfData) do begin
var fields:array of string:=parser.ReadFields(); //Тут можно что-то сделать с полями строки
EMP:=new TEMPLOYEE();
EMP.EMP_NO:=SmallInt.Parse(fields[0]);
EMP.HIRE_DATE:=DateTime.Parse(fields[4]);
EMP.JOB_GRADE:=SmallInt.Parse(fields[7]);
EMP.SALARY:=Decimal.Parse(fields[9]);
EMP.FIRST_NAME:=fields[1];
EMP.LAST_NAME:=fields[2];
EMP.PHONE_EXT:=fields[3];
EMP.DEPT_NO:=fields[5];
EMP.JOB_CODE:=fields[6];
EMP.JOB_COUNTRY:=fields[8];
EMP.FULL_NAME:=fields[10];
EMPS.Add(EMP);
end;
end;
//Сохраняем список объектов на диск в бинарном формате — проводим сериализацию объекта класса ArrayList

using fileStream:=new FileStream(‘C:EMPLOYEE.dat’,FileMode.Create) do begin
(new BinaryFormatter()).Serialize(fileStream,EMPS);
end;
System.Console.ReadLine;
end;
end.

Пойдем дальше — перезапишем наш файл в какой-нибудь экзотический формат. На самом деле это будет бинарный формат (т.е. не текстовый), и тогда полученный файл по размеру окажется больше, чем исходный текстовый. Но идея в данном случае заключается не в экономии жизненного пространства на громадном диске, а в универсальности подхода к хранению информации. Метод сериализации позволяет выбрать оптимальный вариант между экономией места на жестком диске (для данного рассматриваемого случая), правилами безопасности и спецификой бизнес-логики, а именно особенностями структуры данных. Здесь имеется в виду ориентация на структуру, оптимизированную не для хранения, а для обработки данных. Сериализация — хорошее решение для сохранения данных программы, написанной под .NET, во временном промежутке между ее сессиями. Но это далеко не единственное применение такого подхода. В нашем варианте мы выбрали бинарный формат для сериализации и получили новый файл EMPLOYEE.dat, в котором хранятся те же данные, что и в EMPLOYEE.txt, но уже в недоступном для постороннего глаза виде. Однако мы выбрали формат, удобный для объекта класса ArrayList — «навороченного» массива объектов. Для сериализации необязательно пользоваться сложными классами-контейнерами, можно написать свою структуру.

В результате работы программы наши записи будут в новом файле представлять собой отпечатки объектов класса TEMPLOYEE. А чтобы компилятор позволил нам это проделать, в описании данного класса надо не забыть поставить атрибут Serializable.

Кстати, заметили ли вы, что объекту класса TextFieldParser пришлось явно указывать кодировку файла?

Следующий пример будет достаточно простым. Проводим десериализацию объекта класса ArrayList (т.е. выполняем действие, обратное сериализации), убеждаемся, что она получилась, выводя уже знакомые данные в консоль, и записываем снова все на диск, только уже в формате XML.

Листинг 5. Десериализация бинарного файла и сериализация в формате XML

...

class method ConsoleApp.Main;
var EMP:Ex4.TEMPLOYEE; 
EMPS:=new ArrayList();
i:Integer; begin // Читаем бинарный файл — десериализуем его обратно в объект класса ArrayList 
using filestream:=new FileStream(‘C:EMPLOYEE.dat’,FileMode.Open) do begin 
EMPS:=(new BinaryFormatter()).Deserialize(filestream) as ArrayList; 
end; 
for i:=0 to EMPS.Count-1 do begin 
EMP:=(EMPS.Item[i] as TEMPLOYEE); 
System.Console.Writeline( //Выводим в консоль некоторые столбцы. Сложно привыкнуть, когда IDE за вас постоянно расставляет в тексте END к месту и не к месту,иногда приходится долго bскать следы этих диверсий! Выборочно выводим в консоль значения столбцов
String.Format(‘{0}’+Chr(9)+’{1}’+Chr(9)+’{2}’+Chr(9)+’{3}’+Chr(9)+’{4}’, EMP.EMP_NO.ToString, EMP.DEPT_NO.ToString, EMP.HIRE_DATE.ToShortDateString, EMP.FULL_NAME, EMP.SALARY) ); 
end; 
//Сохраняем список объектов на диск //в формате XML, т.е. опять проводим сериализацию, но уже с использованием класса SoapFormatter 
using fileStream:=new FileStream(‘C:EMPLOYEE.xml’,FileMode.Create) do begin 
(new SoapFormatter()).Serialize(fileStream,EMPS);
end;
System.Console.ReadLine;
end;
end.

На выходе получили громадный файл (по сравнению с предыдущими), имеющий, однако, открытый формат — важнейшее из преимуществ, которое дает XML.

Итак

В статье мы тактично обошли вопрос, связанный с особенностями работы с бинарными файлами произвольного формата c использованием класса FileStream, не связанного с сериализацией. Также не рассматривалась похожая тема работы с текстовыми файлами, имеющими поля фиксированной длины, но не имеющими разделителей.

Оба этих вопроса лежат глубже рассматриваемой темы, поскольку связаны с другими технологиями платформы .NET, выходящими далеко за рамки данной статьи.


Литература

  1. Строки форматирования в .NET, www.jenyay.net, www.realcoding.net
  2. Keith Rimington. Basic File IO and the DataGridView, www.codeproject.com
  3. Nitin Kunte. Get System Info using C# , www.codeproject.com
  4. Stephan Depoorter. Handling Fixed-width Flat Files with .NET Custom Attributes, www.codeproject.com
  5. Jeff Brand. Converting CSV Data to Objects, www.codeproject.com
  6. Oleg Axenow.Работа со строками, www.gotdotnet.ru
  7. Savage, Read and Write Structures to Files with .NET, www.codeproject.com
  8. Сергей Иванов, Работа с кодировкой DOS, www.realcoding.net
  9. Jan Schreuder.Using OleDb to Import Text Files (tab, CSV, custom), www.codeproject.com
  10. Jisu74.Read a certain line in a text file, www.codeproject.com
  11. Dreamzor, Getting File Info, www.codeproject.com
  12. How to copy a String into a struct using C#, www.codeproject.com
  13. Arun GG, www.csharphelp.com ,TextReader and TextWriter In C# 
  14. BLaZiNiX, An INI file handling class using C#, www.codeproject.com
  15.  C# 2008 и платформа .NET 3.5 для профессионалов.Christian Nagel, Bill Evjen, Jay Glynn, Karli Watson, Morgan Skinner. Диалектика

Фрагмент иерархии классов, связанных с потоками из пространства имен System.IO