Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
В качестве финального замечания: не забывайте, что обобщения можно обнаружить во многих местах внутри библиотек базовых классов .NET. Здесь мы сосредоточились конкретно на обобщенных коллекциях. Тем не менее, по мере проработки материала оставшихся глав (и освоения платформы) вы наверняка найдете обобщенные классы, структуры и делегаты в том или ином пространстве имен. Кроме того, будьте готовы столкнуться с обобщенными членами в необобщенном классе!
Глава 11
Расширенные средства языка C#
В настоящей главе ваше понимание языка программирования C# будет углублено за счет исследования нескольких более сложных тем. Сначала вы узнаете, как реализовывать и применять индексаторный метод. Такой механизм C# позволяет строить специальные типы, которые предоставляют доступ к внутренним элементам с использованием синтаксиса, подобного синтаксису массивов. Вы научитесь перегружать разнообразные операции (+, -, <, > и т.д.) и создавать для своих типов специальные процедуры явного и неявного преобразования (а также ознакомитесь с причинами, по которым они могут понадобиться).
Затем будут обсуждаться темы, которые особенно полезны при работе с API-интерфейсами LINQ (хотя они применимы и за рамками контекста LINQ): расширяющие методы и анонимные типы.
В завершение главы вы узнаете, каким образом создавать контекст "небезопасного" кода, чтобы напрямую манипулировать неуправляемыми указателями. Хотя использование указателей в приложениях C# — довольно редкое явление, понимание того, как это делается, может пригодиться в определенных обстоятельствах, связанных со сложными сценариями взаимодействия.
Понятие индексаторных методов
Программистам хорошо знаком процесс доступа к индивидуальным элементам, содержащимся внутри простого массива, с применением операции индекса ([]). Вот пример:
// Организовать цикл по аргументам командной строки
// с использованием операции индекса.
for(int i = 0; i < args.Length; i++)
{
Console.WriteLine("Args: {0}", args[i]);
}
// Объявить массив локальных целочисленных значений.
int[] myInts = { 10, 9, 100, 432, 9874};
// Применить операцию индекса для доступа к каждому элементу.
for(int j = 0; j < myInts.Length; j++)
{
Console.WriteLine("Index {0} = {1} ", j, myInts[j]);
}
Console.ReadLine();
Приведенный код ни в коем случае не является чем-то совершенно новым. Но в языке C# предлагается возможность проектирования специальных классов и структур, которые могут индексироваться подобно стандартному массиву, за счет определения индексаторного метода.Такое языковое средство наиболее полезно при создании специальных классов коллекций (обобщенных или необобщенных).
Прежде чем выяснять, каким образом реализуется специальный индексатор, давайте начнем с того, что продемонстрируем его в действии. Пусть к специальному типу PersonCollection, разработанному в главе 10 (в проекте IssuesWithNonGenericCollections), добавлена поддержка индексаторного метода. Хотя сам индексатор пока не добавлен, давайте посмотрим, как он используется внутри нового проекта консольного приложения по имени SimpleIndexer:
using System;
using System.Collections.Generic;
using System.Data;
using SimpleIndexer;
// Индексаторы позволяют обращаться к элементам в стиле массива.
Console.WriteLine("***** Fun with Indexers *****n");
PersonCollection myPeople = new PersonCollection();
// Добавить объекты с применением синтаксиса индексатора.
myPeople[0] = new Person("Homer", "Simpson", 40);
myPeople[1] = new Person("Marge", "Simpson", 38);
myPeople[2] = new Person("Lisa", "Simpson", 9);
myPeople[3] = new Person("Bart", "Simpson", 7);
myPeople[4] = new Person("Maggie", "Simpson", 2);
// Получить и отобразить элементы, используя индексатор.
for (int i = 0; i < myPeople.Count; i++)
{
Console.WriteLine("Person number: {0}", i); // номер лица
Console.WriteLine("Name: {0} {1}",
myPeople[i].FirstName, myPeople[i].LastName); // имя и фамилия
Console.WriteLine("Age: {0}", myPeople[i].Age); // возраст
Console.WriteLine();
}
Как видите, индексаторы позволяют манипулировать внутренней коллекцией подобъектов подобно стандартному массиву. Но тут возникает серьезный вопрос: каким образом сконфигурировать класс PersonCollection (или любой другой класс либо структуру) для поддержки такой функциональности? Индексатор представлен как слегка видоизмененное определение свойства С#. В своей простейшей форме индексатор создается с применением синтаксиса this[]. Ниже показано необходимое обновление класса PersonCollection:
using System.Collections;
namespace SimpleIndexer
{
// Добавить индексатор к существующему определению класса.
public class PersonCollection : IEnumerable
{
private ArrayList arPeople = new ArrayList();
...
// Специальный индексатор для этого класса.
public Person this[int index]
{
get => (Person)arPeople[index];
set => arPeople.Insert(index, value);
}
}
}
Если не считать факт использования ключевого слова this с квадратными скобками, то индексатор похож на объявление любого другого свойства С#. Например, роль области get заключается в возвращении корректного объекта вызывающему коду. Здесь мы достигаем цели делегированием запроса к индексатору объекта ArrayList, т.к. данный класс также поддерживает индексатор. Область set контролирует добавление новых объектов Person, что достигается вызовом метода Insert() объекта ArrayList.
Индексаторы являются еще одной формой "синтаксического сахара", учитывая то, что такую же функциональность можно получить с применением "нормальных" открытых методов наподобие AddPerson() или GetPerson(). Тем не менее, поддержка индексаторных методов в специальных типах коллекций обеспечивает хорошую интеграцию с инфраструктурой библиотек базовых классов .NET Core.
Несмотря на то что создание индексаторных методов является вполне обычным явлением при построении специальных коллекций, не забывайте, что обобщенные типы предлагают такую функциональность в готовом виде. В следующем методе используется обобщенный список List<T> объектов Person. Обратите внимание, что индексатор List<T> можно просто применять непосредственно:
using System.Collections.Generic;
static void UseGenericListOfPeople()
{
List<Person> myPeople = new List<Person>();
myPeople.Add(new Person("Lisa", "Simpson", 9));
myPeople.Add(new Person("Bart", "Simpson", 7));
// Изменить первый объект лица с помощью индексатора.
myPeople[0] = new Person("Maggie", "Simpson", 2);
// Получить и отобразить каждый элемент, используя индексатор.
for (int i = 0; i < myPeople.Count; i++)
{
Console.WriteLine("Person number: {0}", i);