Основы ADO .NET
Изменение данных в DataTable и состояние строки таблицы
Основной контроль за изменениями данных в таблице возлагается на строки – объекты класса DataRow.
Для строки определены несколько состояний, которые объявлены в перечислении RowState. Контроль за сохраняемой в строках таблицы информацией обеспечивается посредством определения состояния строки, которое обеспечивается одноименным ( RowState ) свойством – членом класса DataRow.
Пример. Создание таблицы, работа с записями
using System; using System.Data; namespace Rolls01 { // Работа с таблицей: // определение структуры таблицы, // сборка записи (строки таблицы), // добавление новой записи в таблицу, // индексация записей, // выбор значения поля в строке, // изменение записи. public class RollsData { public DataTable rolls; int rollIndex; public RollsData() { rollIndex = 0; // Создается объект "таблица". rolls = new DataTable("Rolls"); // Задаются и подсоединяются столбики, которые определяют тип таблицы. // Ключевой столбец. DataColumn dc = rolls.Columns.Add("nRoll",typeof(Int32)); dc.AllowDBNull = false; dc.Unique = true; //rolls.PrimaryKey = dc; // Прочие столбцы, значения которых определяют физические // характеристики объектов. rolls.Columns.Add("Victim",typeof(Int32)); // Значения координат. rolls.Columns.Add("X", typeof(Single)); rolls.Columns.Add("Y", typeof(Single)); // Старые значения координат. rolls.Columns.Add("lastX", typeof(Single)); rolls.Columns.Add("lastY", typeof(Single)); // Составляющие цвета. rolls.Columns.Add("Alpha", typeof(Int32)); rolls.Columns.Add("Red", typeof(Int32)); rolls.Columns.Add("Green", typeof(Int32)); rolls.Columns.Add("Blue", typeof(Int32)); } // Добавление записи в таблицу. public void AddRow(int key, int victim, float x, float y, float lastX, float lastY, int alpha, int red, int green, int blue) { // Новая строка создается от имени таблицы, // тип которой определяется множеством ранее // добавленных к таблице столбцов. Подобным образом // созданная строка содержит кортеж ячеек // соответствующего типа. DataRow dr = rolls.NewRow(); // Заполнение ячеек строки. dr["nRoll"] = key; dr["Victim"] = victim; dr["X"] = x; dr["Y"] = y; dr["lastX"] = lastX; dr["lastY"] = lastY; dr["Alpha"] = alpha; dr["Red"] = red; dr["Green"] = green; dr["Blue"] = blue; // Созданная и заполненная строка // подсоединяется к таблице. rolls.Rows.Add(dr); } // Выборка значений очередной строки таблицы. // Ничего особенного. Для доступа к записи (строке) используются // выражения индексации по отношению к множеству Rows. // Для доступа к полю выбранной записи используются // "индексаторы-идентификаторы". public void NextRow(ref rPoint p) { p.index = (int)rolls.Rows[rollIndex]["nRoll"]; p.victim = (int)rolls.Rows[rollIndex]["Victim"]; p.p.X = (float)rolls.Rows[rollIndex]["X"]; p.p.Y = (float)rolls.Rows[rollIndex]["Y"]; p.lastXdirection = (float)rolls.Rows[rollIndex]["lastX"]; p.lastYdirection = (float)rolls.Rows[rollIndex]["lastY"]; p.c.alpha = (int)rolls.Rows[rollIndex]["Alpha"]; p.c.red = (int)rolls.Rows[rollIndex]["Red"]; p.c.green = (int)rolls.Rows[rollIndex]["Green"]; p.c.blue = (int)rolls.Rows[rollIndex]["Blue"]; p.cp.alpha = p.c.alpha – 50; p.cp.red = p.c.red – 10; p.cp.green = p.c.green – 10; p.cp.blue = p.c.blue – 10; rollIndex++; // Изменили значение индекса строки. if (rollIndex == rolls.Rows.Count) rollIndex = 0; } // Та же выборка, но в параметрах дополнительно указан индекс записи. public void GetRow(ref rPoint p, int key) { p.index = (int)rolls.Rows[key]["nRoll"]; p.victim = (int)rolls.Rows[key]["Victim"]; p.p.X = (float)rolls.Rows[key]["X"]; p.p.Y = (float)rolls.Rows[key]["Y"]; p.lastXdirection = (float)rolls.Rows[key]["lastX"]; p.lastYdirection = (float)rolls.Rows[key]["lastY"]; p.c.alpha = (int)rolls.Rows[key]["Alpha"]; p.c.red = (int)rolls.Rows[key]["Red"]; p.c.green = (int)rolls.Rows[key]["Green"]; p.c.blue = (int)rolls.Rows[key]["Blue"]; p.cp.alpha = p.c.alpha – 50; p.cp.red = p.c.red – 10; p.cp.green = p.c.green – 10; p.cp.blue = p.c.blue – 10; if (rollIndex == rolls.Rows.Count) rollIndex = 0; } // Изменяется значение координат и статуса точки. // Значение порядкового номера объекта-параметра используется // в качестве первого индексатора, имя столбца – в // качестве второго. Скорость выполнения операции присваивания // значения ячейке оставляет желать лучшего. public void SetXYStInRow(rPoint p) { rolls.Rows[p.index]["X"] = p.p.X; rolls.Rows[p.index]["Y"] = p.p.Y; rolls.Rows[p.index]["lastX"] = p.lastXdirection; rolls.Rows[p.index]["lastY"] = p.lastYdirection; rolls.Rows[p.index]["Victim"] = p.victim; } public void ReSetRowIndex() { rollIndex = 0; } } }Листинг 18.3.
Relations
В классе DataSet определяется свойство Relations – набор объектов – представителей класса DataRelations. Каждый такой объект определяет связи между составляющими объект DataSet объектами DataTable (таблицами). Если в DataSet более одного набора DataTable, набор DataRelations будет содержать несколько объектов типа DataRelation. Каждый объект определяет связи между таблицами – DataTable. Таким образом, в объекте DataSet реализуется полный набор элементов для управления данными, включая сами таблицы, ограничения и отношения между таблицами.
Constraints
Объекты – представители класса Constraint в наборе Constraints объекта DataTable позволяет задать на множестве объектов DataTable различные ограничения. Например, можно создать объект Constraint, гарантирующий, что значение поля или нескольких полей будут уникальны в пределах DataTable.
DataView
Объекты – представители класса DataView НЕ ПРЕДНАЗНАЧЕНЫ для организации визуализации объектов DataTable.
Их назначение – простой последовательный доступ к строкам таблицы. Объекты DataView являются средством перебора записей таблицы. При обращении ЧЕРЕЗ объект DataView к таблице получают данные, которые хранятся в этой таблице.
DataView нельзя рассматривать как таблицу. DataView не может обеспечить представление таблиц. Также DataView не может обеспечить исключения и добавления столбцов. Таким образом, DataView НЕ является средством преобразования исходной информации, зафиксированной в таблице.
После создания объекта DataView и его настройки на конкретную таблицу появляется возможность перебора записей, их фильтрации, поиска и сортировки.
DataView предоставляет средства динамического представления набора данных, к которому можно применить различные вырианты сортировки и фильтрации на основе критериев, обеспечиваемых базой данных.
Класс DataView обладает большим набором свойств, методов и событий, что позволяет с помощью объекта – представителя класса DataView создавать различные представления данных, содержащихся в DataTable.
Используя этот объект, можно представлять содержащиеся в таблице данные в соответствии с тем или иным порядком сортировки, а также организовать различные варианты фильтрации данных.
DataView предоставляет динамический взгляд на содержимое таблицы в зависимости от установленного в таблице порядка представления и вносимых в таблицы изменений.
Функционально реализация DataView отличается от метода Select, определенного в DataTable, который возвращает массив DataRow (строк).
Для управления установками представления для всех таблиц, входящих в DataSet, используется объект – представитель класса DataViewManager.
DataViewManager предоставляет удобный способ управления параметрами настройки представления по умолчанию для каждой таблицы.
Примеры использования DataView
Для организации просмотра информации, сохраняемой объектом-представителем класса DataTable через объект – представитель класса DataView, этот объект необходимо связать с таблицей.
Таким образом, в приложении создается (независимый!) вьюер, который связывается с таблицей.
Итак, имеем
DataTable tbl = new DataTable("XXX"); // Объявлен и определен объект "таблица". DataView vie; // Ссылка на вьюер. vie = new DataView(); // Создали... vie.Table = tbl; // Привязали таблицу к вьюеру. // Можно и так... vie = new DataView(tbl); // Создали и сразу привязали...
Управление вьюером также осуществляется посредством различных достаточно простых манипуляций, включая изменение свойств объекта. Например, сортировка включается следующим образом:
// Предполагается наличие кнопочных переключателей, // в зависимости от состояния которых в свойстве Sort // вьюера выставляется в качестве значения имя того или // иного столбца. Результат сортировки становится виден // непосредственно после передачи информации объекту, // отвечающему за демонстрацию таблицы (rpDataGrid). if (rBN.Checked) rd.view.Sort = "nRoll"; if (rBX.Checked) rd.view.Sort = "X"; if (rBY.Checked) rd.view.Sort = "Y"; this.rpDataGrid.DataSource = rd.view;
Следующий пример кода демонстрирует возможности поиска, реализуемые объектом – представителем класса DataView. Сортировка обеспечивается вариантами методов Find и FindRows, которые способны в различной реализации воспринимать отдельные значения или массивы значений.
Поиск информации проводится по столбцам, предварительно перечисленным в свойстве Sort. При неудовлетворительном результате поиска метод Find возвращает отрицательное значение, метод FindRows – нулевое.
В случае успеха метода Sort возвращается индекс первой найденной записи. По этому индексу можно получить непосредственный доступ к записи:
// Выставили значение индекса int findIndex = –1; ::::: // Поиск в строке по полю "nRoll" (целочисленный столбец) rd.view.Sort = "nRoll"; try { // Проверка на соответствие типа. int.Parse(this.findTtextBox.Text); // Сам поиск. findIndex = rd.view.Find(this.findTtextBox.Text); } catch (Exception e1) { this.findTtextBox.Text = "Integer value expected..."; } } ::::: // Проверка результатов. if (findIndex == –1) { this.findTtextBox.Text = "Row not found: " + this.findTtextBox.Text; } else { this.findTtextBox.Text = "Yes :" + rd.view[findIndex]["nRoll"].ToString() + "," + rd.view[findIndex]["X"].ToString() + "," + rd.view[findIndex]["Y"].ToString(); }Листинг 18.4.
Применение метода FindRows. В случае успешного завершения поиска возвращается массив записей, элементы которого могут быть выбраны посредством цикла foreach:
// Массив для получения результатов поиска DataRowView[] rows; ::::: // Поиск в строке по полю "nRoll" (целочисленный столбец) rd.view.Sort = "nRoll"; try { // Проверка на соответствие типа. int.Parse(this.findTtextBox.Text); // Сам поиск. Возвращается массив rows. rows = rd.view.FindRows(this.findTtextBox.Text); } catch (Exception e1) { this.findTtextBox.Text = "Integer value expected..."; } } ::::: // Проверка результатов. if (rows.Length == 0) { this.findTtextBox.Text = "No rows found: " + this.findTtextBox.Text; } else { foreach (DataRowView row in rows) { this.findTtextBox.Text = row["nRoll"].ToString() + "," + row["X"].ToString() + "," + row["Y"].ToString(); } }Листинг 18.5.
В примере демонстрируется взаимодействие автономной таблицы и "заточенного" под нее вьюера. Записи в таблицу можно добавлять как непосредственно, так и через вьюер. При этом необходимо совершать некоторое количество дополнительных действий. Через вьюер же организуется поиск записей.
using System; using System.Data; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; namespace Lights01 { public class DataViewForm : System.Windows.Forms.Form { private System.ComponentModel.Container components = null; DataTable dt; // Таблица. DataColumn c1, c2; // Столбцы таблцы. DataRow dr; // Строка таблицы. DataView dv; // Вьюер таблицы. DataRowView rv; // Вьюер строки таблицы. int currentCounter; // Счетчик текущей строки для вьюера таблицы. private System.Windows.Forms.DataGrid dG; private System.Windows.Forms.DataGrid dGforTable; private System.Windows.Forms.Button buttPrev; private System.Windows.Forms.Button buttFirst; private System.Windows.Forms.Button buttLast; private System.Windows.Forms.Button buttonFind; private System.Windows.Forms.TextBox demoTextBox; private System.Windows.Forms.TextBox findTextBox; private System.Windows.Forms.Button buttonAdd; private System.Windows.Forms.Button buttonAcc; private System.Windows.Forms.GroupBox groupBox1; private System.Windows.Forms.GroupBox groupBox2; private System.Windows.Forms.Button buttNext; public DataViewForm() { InitializeComponent(); CreateTable(); dG.DataSource = dv; dGforTable.DataSource = dt; currentCounter = 0; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code // Required method for Designer support – do not modify // the contents of this method with the code editor. private void InitializeComponent() { this.dG = new System.Windows.Forms.DataGrid(); this.demoTextBox = new System.Windows.Forms.TextBox(); this.buttPrev = new System.Windows.Forms.Button(); this.buttNext = new System.Windows.Forms.Button(); this.buttFirst = new System.Windows.Forms.Button(); this.buttLast = new System.Windows.Forms.Button(); this.findTextBox = new System.Windows.Forms.TextBox(); this.buttonFind = new System.Windows.Forms.Button(); this.buttonAdd = new System.Windows.Forms.Button(); this.dGforTable = new System.Windows.Forms.DataGrid(); this.buttonAcc = new System.Windows.Forms.Button(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.groupBox2 = new System.Windows.Forms.GroupBox(); ((System.ComponentModel.ISupportInitialize)(this.dG)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.dGforTable)).BeginInit(); this.groupBox1.SuspendLayout(); this.groupBox2.SuspendLayout(); this.SuspendLayout(); // // dG // this.dG.DataMember = ""; this.dG.HeaderForeColor = System.Drawing.SystemColors.ControlText; this.dG.Location = new System.Drawing.Point(8, 80); this.dG.Name = "dG"; this.dG.Size = new System.Drawing.Size(280, 128); this.dG.TabIndex = 0; this.dG.MouseDown += new System.Windows.Forms.MouseEventHandler(this.dG_MouseDown); // // demoTextBox // this.demoTextBox.Location = new System.Drawing.Point(152, 48); this.demoTextBox.Name = "demoTextBox"; this.demoTextBox.Size = new System.Drawing.Size(128, 20); this.demoTextBox.TabIndex = 1; this.demoTextBox.Text = ""; // // buttPrev // this.buttPrev.Location = new System.Drawing.Point(189, 16); this.buttPrev.Name = "buttPrev"; this.buttPrev.Size = new System.Drawing.Size(25, 23); this.buttPrev.TabIndex = 2; this.buttPrev.Text = "<–"; this.buttPrev.Click += new System.EventHandler(this.buttPrev_Click); // // buttNext // this.buttNext.Location = new System.Drawing.Point(221, 16); this.buttNext.Name = "buttNext"; this.buttNext.Size = new System.Drawing.Size(25, 23); this.buttNext.TabIndex = 3; this.buttNext.Text = "–>"; this.buttNext.Click += new System.EventHandler(this.buttNext_Click); // // buttFirst // this.buttFirst.Location = new System.Drawing.Point(157, 16); this.buttFirst.Name = "buttFirst"; this.buttFirst.Size = new System.Drawing.Size(25, 23); this.buttFirst.TabIndex = 4; this.buttFirst.Text = "<<"; this.buttFirst.Click += new System.EventHandler(this.buttFirst_Click); // // buttLast // this.buttLast.Location = new System.Drawing.Point(253, 16); this.buttLast.Name = "buttLast"; this.buttLast.Size = new System.Drawing.Size(25, 23); this.buttLast.TabIndex = 5; this.buttLast.Text = ">>"; this.buttLast.Click += new System.EventHandler(this.buttLast_Click); // // findTextBox // this.findTextBox.Location = new System.Drawing.Point(8, 48); this.findTextBox.Name = "findTextBox"; this.findTextBox.Size = new System.Drawing.Size(128, 20); this.findTextBox.TabIndex = 6; this.findTextBox.Text = ""; // // buttonFind // this.buttonFind.Location = new System.Drawing.Point(88, 16); this.buttonFind.Name = "buttonFind"; this.buttonFind.Size = new System.Drawing.Size(48, 23); this.buttonFind.TabIndex = 7; this.buttonFind.Text = "Find"; this.buttonFind.Click += new System.EventHandler(this.buttonFind_Click); // // buttonAdd // this.buttonAdd.Location = new System.Drawing.Point(8, 16); this.buttonAdd.Name = "buttonAdd"; this.buttonAdd.Size = new System.Drawing.Size(40, 23); this.buttonAdd.TabIndex = 8; this.buttonAdd.Text = "Add"; this.buttonAdd.Click += new System.EventHandler(this.buttonAdd_Click); // // dGforTable // this.dGforTable.DataMember = ""; this.dGforTable.HeaderForeColor = System.Drawing.SystemColors.ControlText; this.dGforTable.Location = new System.Drawing.Point(8, 24); this.dGforTable.Name = "dGforTable"; this.dGforTable.Size = new System.Drawing.Size(272, 120); this.dGforTable.TabIndex = 9; // // buttonAcc // this.buttonAcc.Location = new System.Drawing.Point(8, 152); this.buttonAcc.Name = "buttonAcc"; this.buttonAcc.Size = new System.Drawing.Size(40, 23); this.buttonAcc.TabIndex = 10; this.buttonAcc.Text = "Acc"; this.buttonAcc.Click += new System.EventHandler(this.buttonAcc_Click); // // groupBox1 // this.groupBox1.Controls.Add(this.buttonAcc); this.groupBox1.Controls.Add(this.dGforTable); this.groupBox1.Location = new System.Drawing.Point(6, 8); this.groupBox1.Name = "groupBox1"; this.groupBox1.Size = new System.Drawing.Size(298, 184); this.groupBox1.TabIndex = 11; this.groupBox1.TabStop = false; this.groupBox1.Text = "Таблица как она есть "; // // groupBox2 // this.groupBox2.Controls.Add(this.buttPrev); this.groupBox2.Controls.Add(this.buttonFind); this.groupBox2.Controls.Add(this.buttFirst); this.groupBox2.Controls.Add(this.buttLast); this.groupBox2.Controls.Add(this.demoTextBox); this.groupBox2.Controls.Add(this.buttNext); this.groupBox2.Controls.Add(this.dG); this.groupBox2.Controls.Add(this.buttonAdd); this.groupBox2.Controls.Add(this.findTextBox); this.groupBox2.Location = new System.Drawing.Point(8, 200); this.groupBox2.Name = "groupBox2"; this.groupBox2.Size = new System.Drawing.Size(296, 216); this.groupBox2.TabIndex = 12; this.groupBox2.TabStop = false; this.groupBox2.Text = "Вид через вьюер"; // // DataViewForm // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(312, 421); this.Controls.Add(this.groupBox2); this.Controls.Add(this.groupBox1); this.Name = "DataViewForm"; this.Text = "DataViewForm"; ((System.ComponentModel.ISupportInitialize)(this.dG)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.dGforTable)).EndInit(); this.groupBox1.ResumeLayout(false); this.groupBox2.ResumeLayout(false); this.ResumeLayout(false); } #endregion private void CreateTable() { // Создается таблица. dt = new DataTable("Items"); // Столбцы таблицы... // Имя первого столбца – id, тип значения – System.Int32. c1 = new DataColumn("id", Type.GetType("System.Int32")); c1.AutoIncrement=true; // Имя второго столбца – Item, тип значения – System.Int32. c2 = new DataColumn("Item", Type.GetType("System.Int32")); // К таблице добавляются объекты-столбцы... dt.Columns.Add(c1); dt.Columns.Add(c2); // А вот массив столбцов (здесь он из одного элемента) // для организации первичного ключа (множества первичных ключей). DataColumn[] keyCol= new DataColumn[1]; // И вот, собственно, как в таблице задается множество первичных ключей. keyCol[0]= c1; // Свойству объекта t передается массив, содержащий столбцы, которые // формируемая таблица t будет воспринимать как первичные ключи. dt.PrimaryKey=keyCol; // В таблицу добавляется 10 rows. for(int i = 0; i <10;i++) { dr=dt.NewRow(); dr["Item"]= i; dt.Rows.Add(dr); } // Принять изменения. // Так производится обновление таблицы. // Сведения о новых изменениях и добавлениях будут фиксироваться // вплоть до нового обновления. dt.AcceptChanges(); // Здесь мы применим специализированный конструктор, который // задаст значения свойств Table, RowFilter, Sort, RowStateFilter // объекта DataView в двух операторах кода... //dv = new DataView(dt); // Вместо этого... // Определение того, что доступно через объект - представитель DataView. // Задавать можно в виде битовой суммы значений. И не все значения сразу! // Сумма всех значений – противоречивое сочетание! // А можно ли делать это по отдельности? DataViewRowState dvrs = DataViewRowState.Added | DataViewRowState.CurrentRows | DataViewRowState.Deleted | DataViewRowState.ModifiedCurrent | //DataViewRowState.ModifiedOriginal | //DataViewRowState.OriginalRows | //DataViewRowState.None | // Записи не отображаются. DataViewRowState.Unchanged; // Вот такое хитрое объявление... // Таблица, // | значение, относительно которого проводится сортировка, // | | // | | имя столбца, значения которого сортируются, // | | | // | | | составленное значение DataViewRowState. // | | | | dv = new DataView(dt, "", "Item", dvrs); } private void buttNext_Click(object sender, System.EventArgs e) { if (currentCounter+1 < dv.Count) currentCounter++; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } private void buttPrev_Click(object sender, System.EventArgs e) { if (currentCounter–1 >= 0) currentCounter––; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } private void buttFirst_Click(object sender, System.EventArgs e) { currentCounter = 0; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } private void buttLast_Click(object sender, System.EventArgs e) { currentCounter =dv.Count – 1; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } private void dG_MouseDown (object sender, System.Windows.Forms.MouseEventArgs e) { currentCounter = dG.CurrentRowIndex; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } // Реализация поиска требует специального опеделения порядка // сортировки строк, который должен задаваться в конструкторе. private void buttonFind_Click(object sender, System.EventArgs e) { int findIndex = –1; // Сначала проверяем строку на соответствие формату отыскиваемого // значения. // В нашем случае строка должна преобразовываться в целочисленное // значение. try { int.Parse(findTextBox.Text); } catch { findTextBox.Text = "Неправильно задан номер..."; return; } findIndex = dv.Find(findTextBox.Text); if (findIndex >= 0) { currentCounter = findIndex; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } else { findTextBox.Text = "Не нашли."; } } private void buttonAdd_Click(object sender, System.EventArgs e) { // При создании новой записи средствами вьюера таблицы, // связанный с ним вьюер строки переходит в состояние rv.IsNew. // При этом в действиях этих объектов есть своя логика. // И если туда не вмешиваться, при создании очередной записи // предыдущая запись считается принятой и включается в таблицу // автоматически. // Контролируя состояния вьюера строки (rv.IsEdit || rv.IsNew), // мы можем предотвратить процесс последовательного автоматического // обновления таблицы. Все под контролем. if (rv.IsEdit || rv.IsNew) return; rv = dv.AddNew(); rv["Item"] = dv.Count–1; } private void buttonAcc_Click(object sender, System.EventArgs e) { // И вот мы вмешались в процесс. // Добавление новой записи в таблицу становится возможным лишь // после явного завершения редактирования предыдущей записи. // Без этого попытки создания новой записи блокируются. // Завершить редактирование. rv.EndEdit(); // Принять изменения. // Так производится обновление таблицы. // Сведения о новых изменениях и добавлениях будут фиксироваться // вплоть до нового обновления. dt.AcceptChanges(); } } }Листинг 18.6.