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

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 126 127 128 129 130 131 132 133 134 ... 407
имен System.Collections.Specialized

System.Collections — не единственное пространство имен .NET Core, которое содержит необобщенные классы коллекций. В пространстве имен System.Collections.Specialized определено несколько специализированных типов коллекций. В табл. 10.3 описаны наиболее полезные типы в этом конкретном пространстве имен, которые все являются необобщенными.

Кроме указанных конкретных типов классов пространство имен System.Collections.Specialized также содержит много дополнительных интерфейсов и абстрактных базовых классов, которые можно применять в качестве стартовых точек для создания специальных классов коллекций. Хотя в ряде ситуаций такие "специализированные" типы могут оказаться именно тем, что требуется в ваших проектах, здесь они рассматриваться не будут. И снова во многих ситуациях вы с высокой вероятностью обнаружите, что пространство имен System.Collections.Generic предлагает классы с похожей функциональностью, но с добавочными преимуществами.

На заметку! В библиотеках базовых классов .NET Core доступны два дополнительных пространства имен, связанные с коллекциями (System.Collections.ObjectModel и System.Collections.Concurrent). Первое из них будет объясняться позже в главе, когда вы освоите тему обобщений. Пространство имен System.Collections.Concurrent предоставляет классы коллекций, хорошо подходящие для многопоточной среды (многопоточность обсуждается в главе 15).

Проблемы, присущие необобщенным коллекциям

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

Первая проблема заключается в том, что использование классов коллекций System.Collections и System.Collections.Specialized в результате дает код с низкой производительностью, особенно в случае манипулирования числовыми данными (например, типами значений). Как вы вскоре увидите, когда структуры хранятся в любом необобщенном классе коллекции, прототипированном для оперирования с System.Object, среда CoreCLR должна осуществлять некоторое количество операций перемещения в памяти, что может нанести ущерб скорости выполнения.

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

Прежде чем вы увидите, как применять обобщения в своих программах, полезно чуть глубже рассмотреть недостатки необобщенных классов коллекций, что поможет лучше понять проблемы, которые был призван решить механизм обобщений. Создайте новый проект консольного приложения по имени IssuesWithNongenericCollections, импортируйте пространства имен System и System.Collections в начале файла Program.cs и удалите оставшийся код:

using System;

using System.Collections;

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

Как уже было указано в главе 4, платформа .NET Core поддерживает две обширные категории данных: типы значений и ссылочные типы. Поскольку в .NET Core определены две основные категории типов, временами возникает необходимость представить переменную одной категории как переменную другой категории. Для этого в C# предлагается простой механизм, называемый упаковкой (boxing), который позволяет хранить данные типа значения внутри ссылочной переменной. Предположим, что в методе по имени SimpleBoxUnboxOperation() создана локальная переменная типа int. Если где-то в приложении понадобится представить такой тип значения как ссылочный тип, то значение придется упаковать:

static void SimpleBoxUnboxOperation()

{

  // Создать переменную ValueType (int).

  int myInt = 25;

  // Упаковать int в ссылку на object.

  object boxedInt = myInt;

}

Упаковку можно формально определить как процесс явного присваивания данных типа значения переменной System.Object. При упаковке значения среда CoreCLR размещает в куче новый объект и копирует в него величину типа значения (в данном случае 25). В качестве результата возвращается ссылка на вновь размещенный в куче объект.

Противоположная операция также разрешена и называется распаковкой (unboxing). Распаковка представляет собой процесс преобразования значения, хранящегося в объектной ссылке, обратно в соответствующий тип значения в стеке. Синтаксически операция распаковки выглядит как обычная операция приведения, но ее семантика несколько отличается. Среда CoreCLR начинает с проверки того, что полученный тип данных эквивалентен упакованному типу, и если это так, то копирует значение в переменную, находящуюся в стеке. Например, следующие операции распаковки работают успешно при условии, что лежащим в основе типом boxedInt действительно является int:

static void SimpleBoxUnboxOperation()

{

  // Создать переменную ValueType (int).

  int myInt = 25;

  // Упаковать int в ссылку на object.

  object boxedInt = myInt;

  // Распаковать ссылку обратно в int.

  int unboxedInt = (int)boxedInt;

}

Когда компилятор C# встречает синтаксис упаковки/распаковки, он выпускает код CIL, который содержит коды операций box/unbox. Если вы просмотрите сборку с помощью утилиты ildasm.exe, то обнаружите в ней показанный далее код CIL:

.method assembly hidebysig static

    void  '<<Main>$>g__SimpleBoxUnboxOperation|0_0'() cil managed

{

  .maxstack  1

  .locals init (int32 V_0, object V_1, int32 V_2)

    IL_0000:  nop

    IL_0001:  ldc.i4.s   25

    IL_0003:  stloc.0

    IL_0004:  ldloc.0

    IL_0005:  box        [System.Runtime]System.Int32

    IL_000a:  stloc.1

    IL_000b:  ldloc.1

    IL_000c:  unbox.any  [System.Runtime]System.Int32

    IL_0011:  stloc.2

    IL_0012:  ret

  } // end of method '<Program>$'::'<<Main>$>g__SimpleBoxUnboxOperation|0_0'

Помните, что в отличие от обычного приведения распаковка обязана осуществляться только в подходящий тип данных. Попытка распаковать порцию данных в некорректный тип приводит к генерации исключения InvalidCastException. Для обеспечения высокой безопасности каждая операция распаковки должна быть помещена внутрь конструкции try/catch, но такое действие со всеми операциями распаковки в приложении может оказаться достаточно трудоемкой задачей. Ниже показан измененный код, который выдаст ошибку из-за того, что в нем предпринята попытка распаковки упакованного значения int в тип long:

static void SimpleBoxUnboxOperation()

{

  // Создать переменную ValueType (int).

  int myInt = 25;

  // Упаковать int в ссылку на object.

  object boxedInt = myInt;

  // Распаковать в неподходящий тип данных, чтобы

  // инициировать исключение времени выполнения.

  try

  {

    long unboxedLong = (long)boxedInt;

 

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

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