Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

// обратно в данные, расположенные в стеке.

int i = (int)myInts[0];

// Теперь значение вновь упаковывается, т.к.

// метод WriteLine требует типа object!

Console.WriteLine("Value of your int: {0}", i);

}

Обратите внимание, что расположенное в стеке значение типа

System.Int32
перед вызовом метода
ArrayList.Add
упаковывается, чтобы оно могло быть передано в требуемом виде
System.Object
.
Вдобавок объект
System.Object
распаковывается обратно в
System.Int32
после его извлечения из
ArrayList
через операцию приведения лишь для того, чтобы снова быть упакованными при передаче методу
Console.WriteLine
, поскольку данный метод работает с типом
System.Object
.

Упаковка и распаковка удобны с точки зрения программиста, но такой упрощенный подход к передаче данных между стеком и кучей влечет за собой проблемы, связанные с производительностью (снижение скорости выполнения и увеличение размера кода), а также приводит к утрате безопасности в отношении типов. Чтобы понять проблемы с производительностью, примите во внимание действия, которые должны произойти при упаковке и распаковке простого целочисленного значения.

1. Новый объект должен быть размещен в управляемой куче.

2. Значение данных, находящееся в стеке, должно быть передано в выделенное место в памяти.

3. При распаковке значение, которое хранится в объекте, находящемся в куче, должно быть передано обратно в стек.

4. Неиспользуемый в дальнейшем объект, расположенный в куче, будет (со временем) удален сборщиком мусора.

Несмотря на то что показанный конкретный метод

WorkWithArrayList
не создает значительное узкое место в плане производительности, вы определенно заметите такое влияние, если
ArrayList
будет содержать тысячи целочисленных значений, которыми программа манипулирует на регулярной основе. В идеальном мире мы могли бы обрабатывать данные, находящиеся внутри контейнера в стеке, безо всяких проблем с производительностью. Было бы замечательно иметь возможность извлекать данные из контейнера, не прибегая к конструкциям
try/catch
(именно это позволяют делать обобщения).

Проблема безопасности в отношении типов

Мы уже затрагивали проблему безопасности в отношении типов, когда рассматривали операции распаковки. Вспомните, что данные должны быть распакованы в тот же самый тип, с которым они объявлялись перед упаковкой. Однако существует еще один аспект безопасности в отношении типов, который необходимо иметь в виду в мире без обобщений: тот факт, что классы из пространства имен

System.Collections
обычно могут хранить любые данные, т.к. их члены прототипированы для оперирования с типом
System.Object
. Например, следующий метод строит список
ArrayList
с произвольными фрагментами несвязанных данных:

static void ArrayListOfRandomObjects

{

// ArrayList может хранить вообще все что угодно.

ArrayList allMyObjects = new ArrayList;

allMyObjects.Add(true);

allMyObjects.Add(new OperatingSystem(PlatformID.MacOSX,

new Version(10, 0)));

allMyObjects.Add(66);

allMyObjects.Add(3.14);

}

В ряде случаев вам будет требоваться исключительно гибкий контейнер, который способен хранить буквально все (как было здесь показано). Но большую часть времени вас интересует безопасный в отношении типов

контейнер, который может работать только с определенным типом данных. Например, вы можете нуждаться в контейнере, хранящем только объекты типа подключения к базе данных, растрового изображения или класса, реализующего интерфейс
IPointy
.

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

Person
:

namespace IssuesWithNonGenericCollections

{

public class Person

{

public int Age {get; set;}

public string FirstName {get; set;}

public string LastName {get; set;}

public Person{}

public Person(string firstName, string lastName, int age)

{

Age = age;

FirstName = firstName;

LastName = lastName;

}

public override string ToString

{

return $"Name: {FirstName} {LastName}, Age: {Age}";

}

}

}

Чтобы построить коллекцию, которая способна хранить только объекты

Person
, можно определить переменную-член
System.Collection.ArrayList
внутри класса по имени
PeopleCollection
и сконфигурировать все члены для оперирования со строго типизированными объектами
Person
, а не с объектами типа
System.Object
. Ниже приведен простой пример (специальная коллекция производственного уровня могла бы поддерживать множество дополнительных членов и расширять абстрактный базовый класс из пространства имен
System.Collections
или
System.Collections.Specialized
):

using System.Collections;

namespace IssuesWithNonGenericCollections

{

public class PersonCollection : IEnumerable

{

private ArrayList arPeople = new ArrayList;

// Приведение для вызывающего кода.

public Person GetPerson(int pos) => (Person)arPeople[pos];

// Вставка только объектов Person.

public void AddPerson(Person p)

{

arPeople.Add(p);

}

public void ClearPeople

{

arPeople.Clear;

}

public int Count => arPeople.Count;

// Поддержка перечисления с помощью foreach.

IEnumerator IEnumerable.GetEnumerator => arPeople.GetEnumerator;

Поделиться с друзьями: