Рейтинговые книги
Читем онлайн Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 127 128 129 130 131 132 133 134 135 ... 407
}

  catch (InvalidCastException ex)

  {

    Console.WriteLine(ex.Message);

  }

}

На первый взгляд упаковка/распаковка может показаться довольно непримечательным средством языка, с которым связан больше академический интерес, нежели практическая ценность. В конце концов, необходимость хранения локального типа значения в локальной переменной object будет возникать нечасто. Тем не менее, оказывается, что процесс упаковки/распаковки очень полезен, поскольку позволяет предполагать, что все можно трактовать как System.Object, а среда CoreCLR самостоятельно позаботится о деталях, касающихся памяти.

Давайте обратимся к практическому применению описанных приемов. Мы будем исследовать класс System.Collections.ArrayList и использовать его для хранения порции числовых (расположенных в стеке) данных. Соответствующие члены класса ArrayList перечислены ниже. Обратите внимание, что они прототипированы для работы с данными типа System.Object. Теперь рассмотрим методы Add(), Insert() и Remove(), а также индексатор класса:

public class ArrayList : IList, ICloneable

{

...

  public virtual int Add(object? value);

  public virtual void Insert(int index, object? value);

  public virtual void Remove(object? obj);

  public virtual object? this[int index] {get; set; }

}

Класс ArrayList был построен для оперирования с экземплярами object, которые представляют данные, находящиеся в куче, поэтому может показаться странным, что следующий код компилируется и выполняется без ошибок:

static void WorkWithArrayList()

{

  // Типы значений автоматически упаковываются при передаче

  // методу, который требует экземпляр типа object.

  ArrayList myInts = new ArrayList();

  myInts.Add(10);

  myInts.Add(20);

  myInts.Add(35);

}

Хотя здесь числовые данные напрямую передаются методам, которые требуют экземпляров типа object, исполняющая среда выполняет автоматическую упаковку таких основанных на стеке данных. Когда позже понадобится извлечь элемент из ArrayList с применением индексатора типа, находящийся в куче объект должен быть распакован в целочисленное значение, расположенное в стеке, посредством операции приведения. Не забывайте, что индексатор ArrayList возвращает элементы типа System.Object, а не System.Int32:

static void WorkWithArrayList()

{

  // Типы значений автоматически упаковываются,

  // когда передаются члену, принимающему object.

  ArrayList myInts = new ArrayList();

  myInts.Add(10);

  myInts.Add(20);

  myInts.Add(35);

  // Распаковка происходит, когда объект преобразуется

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

  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}";

    }

  }

}

Чтобы построить

1 ... 127 128 129 130 131 132 133 134 135 ... 407
На этой странице вы можете бесплатно читать книгу Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен бесплатно.

Оставить комментарий