Эффективность и безопасность типов обобщений

Вероятно, дополнительная эффективность при использовании типов значений в коллекциях — одно из наибольших преимуществ, которые несут с собой обобщения в С#. Поскольку обычный массив, основанный на System.Array, может содержать гетерогенные коллекции экземпляров, созданных из множества типов, до тех пор, пока он хранит ссылки на некоторый общий для них всех базовый тип вроде System.Object, он обладает существенными недостатками.

Рассмотрим следующий пример применения:

1
2
3
4
5
public void SomeMethod ( ArrayList col )  {
foreach ( object о in col )  {
ISomelnterface iface = (ISomelnterface) o;
о.DoSomething();
}

Поскольку все в CLR наследуется от System.Object, экземпляр ArrayList, переданный через переменную col, может содержать просто невообразимую смесь разных сущностей. Некоторые из них могут и не реализовывать интерфейс ISomelnterf асе. И тогда, как можно было ожидать, такой код сгенерирует исключение InvalidCastException.

Однако разве плохо было бы заставить механизм контроля типов компилятора С# выявлять такие вещи еще на этапе компиляции? Именно это и делают обобщения. С помощью обобщений можно написать примерно такой код:

1
2
3
4
public void SomeMethod ( IList<isomeInterfасе> col )  {
foreach( ISomelnterface iface in col )  {
о.DoSomething();
}

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

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

Ту же проблему можно решить и без использования обобщений, но это потребовало бы написания вручную класса, который служил бы той же цели, что и сконструированный тип List. Поэтому другая прелесть обобщений подобна шаблонам С++: они представляют собой оболочку для легкой специализации новых типов, построенных на их основе, таким образом, способствуя повторному использованию кода.

Компилятор — ваш друг, и вы всегда должны предоставлять ему как можно больше информации о типах, чтобы помочь ему выполнять свою работу. Поскольку в С# и CLR все, так или иначе, наследуется от System.Object, всегда легко с помощью приведения удалить из объектов информацию о типе, тем самым вводя компилятор в заблуждение. Если вы пришли из среды С++, просто подумайте, что может случиться, если вы станете передавать все указатели как void. При этом еще не были упомянуты трудно поддающиеся поиску ошибки, которые неизбежны при таком неразумном подходе.

Приведенный выше пример показывает, как использовать обобщения для обеспечения безопасности типов. Однако в этом случае большой выигрыш в смысле эффективности не получится. Реальное повышение эффективности происходит тогда, когда аргумент-тип является типом значений. Вспомните, что тип значения, вставляемый в коллекцию из пространства имен System.Collections, такую как ArrayList, сначала должен быть упакован, поскольку ArrayList поддерживает коллекцию типов System.Object.

Коллекция ArrayList, предназначенная для хранения только целых чисел, страдает от нескольких проблем, связанных с эффективностью, поскольку целочисленные значения должны упаковываться и распаковываться при каждой вставке и извлечении из коллекции. К тому же, операция распаковки в С# обычно формируется в виде IL-операции unbox, сопровождаемой операцией копирования данных типа значения. Вдобавок вся эта упаковка увеличивает нагрузку на управляемую кучу.

На помощь здесь приходят обобщения. В качестве примера скомпилируйте следующий код и затем загрузите сборку в ILDASM, чтобы сравнить сгенерированный код IL для каждого метода, принимающего экземпляр Stack:

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using System.Collections;
using System.Collections.Generic-public class EntryPoint {
static void Main()  { }
public void NonGeneric ( Stack stack )  {
foreach( object о in stack )  {
int number = (int) o;
Console.WriteLine ( number );
}
public void Generic ( Stack<int> stack )  {
foreach( int number in stack )  {
Console.WriteLine( number );
}

Обратите внимание, что код IL, сгенерированный методом NonGeneric, содержит, по меньшей мере, на 10 инструкций больше, чем обобщенная версия. Большая их часть предназначена для упаковки и распаковки элементов, которой приходится заниматься NonGeneric. Кроме того, метод NonGeneric может потенциально сгенерировать исключение InvalidCa st Except ion, если встретит объект, который не может быть явно приведен и распакован в целое число во время выполнения.

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

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

Соглашения об именовании указателей мест заполнения в обобщенных типах

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

Это соглашение о назначении имен подобно принятому для обозначения интерфейсов, где имена начинаются с заглавной I, что облегчает читабельность кода. Если определение обобщенного типа включает только один параметр типа, и понять его нетрудно, принято называть его т.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100