обычно наполняются без вашего участия с использованием связанного "адаптера данных", в приведенном ниже коде показано, как вручную создать находящийся в памяти объект
DataTable
, который содержит три столбца (для имени, фамилии и возраста каждой записи). Обратите внимание на то, как после добавления одной строки в
DataTable
с помощью многомерного индексатора производится обращение ко всем столбцам первой (и единственной) строки. (Если вы собираетесь следовать примеру, тогда импортируйте в файл кода пространство имен
System.Data
.)
static void MultiIndexerWithDataTable
{
//
Создать простой объект DataTable с тремя столбцами.
DataTable myTable = new DataTable;
myTable.Columns.Add(new DataColumn("FirstName"));
myTable.Columns.Add(new DataColumn("LastName"));
myTable.Columns.Add(new DataColumn("Age"));
// Добавить строку в таблицу.
myTable.Rows.Add("Mel", "Appleby", 60);
// Использовать многомерный индексатор для вывода деталей первой строки.
Начиная с главы 21, мы продолжим рассмотрение ADO.NET, так что не переживайте, если что-то в приведенном выше коде выглядит незнакомым. Пример просто иллюстрирует, что индексаторные методы способны поддерживать множество измерений, а при правильном применении могут упростить взаимодействие с объектами, содержащимися в специальных коллекциях.
Определения индексаторов в интерфейсных типах
Индексаторы могут определяться в выбранном типе интерфейса .NET Core, чтобы позволить поддерживающим типам предоставлять специальные реализации. Ниже показан простой пример интерфейса, который задает протокол для получения строковых объектов с использованием числового индексатора:
public interface IStringContainer
{
string this[int index] { get; set; }
}
При таком определении интерфейса любой класс или структура, которые его реализуют, должны поддерживать индексатор с чтением/записью, манипулирующий элементами с применением числового значения. Вот частичная реализация класса подобного вида:
class SomeClass : IStringContainer
{
private List<string> myStrings = new List<string>;
public string this[int index]
{
get => myStrings[index];
set => myStrings.Insert(index, value);
}
}
На этом первая крупная тема настоящей главы завершена. А теперь давайте перейдем к исследованиям языкового средства, которое позволяет строить специальные классы и структуры, уникальным образом реагирующие на внутренние операции С#. Итак, займемся концепцией перегрузки операций.
Понятие перегрузки операций
Как и любой язык программирования, C#
имеет заготовленный набор лексем, используемых для выполнения базовых операций над встроенными типами. Например, вы знаете, что операция
+
может применяться к двум целым числам, чтобы получить большее целое число:
// Операция + с целыми числами.
int a = 100;
int b = 240;
int c = a + b; // с теперь имеет значение 340
Опять-таки, здесь нет ничего нового, но задумывались ли вы когда-нибудь о том, что одну и ту же операцию
+
разрешено использовать с большинством встроенных типов данных С#? Скажем, взгляните на следующий код:
// Операция + со строками.
string s1 = "Hello";
string s2 = " world!";
string s3 = s1 + s2; // s3 теперь имеет значение "Hello World!"
Операция
+
функционирует специфическим образом на основе типа предоставленных данных (в рассматриваемом случае строкового или целочисленного). Когда операция
+
применяется к числовым типам, в результате выполняется суммирование операндов, а когда к строковым типам — то конкатенация строк.
Язык C# дает возможность строить специальные классы и структуры, которые также уникально реагируют на один и тот же набор базовых лексем (вроде операции
+
). Хотя не каждая операция C# может быть перегружена, перегрузку допускают многие операции (табл. 11.1).
Перегрузка бинарных операций
Чтобы проиллюстрировать процесс перегрузки бинарных операций, рассмотрим приведенный ниже простой класс
Point
, который определен в новом проекте консольного приложения по имени
OverloadedOps
:
using System;
namespace OverloadedOps
{
// Простой будничный класс С#.
public class Point
{
public int X {get; set;}
public int Y {get; set;}
public Point(int xPos, int yPos)
{
X = xPos;
Y = yPos;
}
public override string ToString
=> $"[{this.X}, {this.Y}]";
}
}
Рассуждая логически, суммирование объектов
Point
имеет смысл. Например, сложение двух переменных
Point
должно давать новый объект
Point
с просуммированными значениями свойств
X
и
Y
. Конечно, полезно также и вычитать один объект
Point
из другого. В идеале желательно иметь возможность записи примерно такого кода: