Главная » C# » Обобщения .NET

0

В главе 9 мы рассмотрели использование списков, делегатов и лямбда-выражений. Там же был показан пример обобщений .NET при использовании списков для управления коллекцией объектов. В этой главе мы рассмотрим обобщения  .NET более подробно, включая использование их в контексте черного ящика (т. е. когда код ничего не знает о специфике типов параметров обобщений .NET). Кроме этого, будет представлена более подробно реализация лямбда-выражений. Для демонсации этих концепций мы расширим пример электронной таблицы,  использоваый  в  главе  9.  Цель —  приобретение  хорошего  опыта  в  применении  обобщений и лямбда-выражений .NET, которые вы, скорее всего, будете использовать в своем рабочем коде. Это позволит снизить возможность возникновения неприятных сюризов при разработке проектов.

Зачем использовать обобщения .NET?

В принципе, в использовании обобщений .NET нет настоятельной необходимости, и поэтому можно предположить, что эту главу можно было бы и пропустить. Но это будет ошибочное предположение. С такой же легкостью можно было бы сказать, что нет настоятельной необходимости в использовании свойств или любых других структур С#, расширяющих возможности программирования. Использование таких конструкций, как свойства С# и обобщения .NET, предоставляет нам возможности элегантного и выразительного программирования. Чтобы лучше понять, что я паюсь сказать, взгляните на следующую фразу:

Утки ходят  плоские лапы  крякают  громко.

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

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

Добавление обобщений .NET в С# делают его подобным следующему предложению:

Утки ходят смешно, потому, что у  них  плоские  лапы,  а  когда  они  крякают,  то очень   громко.

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

.NET. Если же вы противоположного мнения, то можете без колебаний пропустить данную главу и перейти к следующей.

Примером, иллюстрирующим, как использование обобщений .NET делает код как более ясным, так и более кратким, чем код, не использующий  обобщений  .NET, может служить такая структура, как контейнер. Контейнер — это тип, используый для управления другими типами. Списки и коллекции являются примерами контейнера. Для простоты, рассмотрим контейнер, содержащий лишь один  элент. Далее приводится краткая версия, использующая тип object:

public class Container { object _managed;

public Container(object toManage) {

_managed = toManage;

}

public object Managed { get {

return _managed;

}

}

}

В данном  коде класс container имеет конструктор с параметром и одним свойсом, Managed, которое ссылается на переменную „managed. Целью данного класса является присвоить в конструкторе значение объекту, к которому можно обращатя  с  помощью  свойства.  Класс  Container не  знает,  ни  что  делает  переменная

_managed, ни  каковы  ее  возможности.  Но  ему  это  и  не  нужно  знать,  т. к.  класс

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

Класс container можно использовать следующим образом:

Container container = new Container(new MyType());

(container.Managed as MyType).Method));

При создании экземпляра класса Container члену данных „managed присваивается экземпляр типа мутуре. Тип мутуре используется в демонстрационных целях и имеет один метод — Method (). Чтобы получить и использовать управляемый тип, выпол-

няется обращение к свойству Managed. Но тогда метод Method о нельзя вызывать непосредственно, т. к. свойство Managed имеет тип object; поэтому необходимо выполнить приведение типа свойства к типу МуТуре, чтобы сделать вызов метода Method () легитимным.

Результатом приведения типа с помощью оператора as будет действительный эемпляр типа мутуре или значение null, что вызовет исключение обращения к невому объекту. Поэтому для безопасного использования свойства Managed нужен следующий код:

Container container = new Container(new МуТуре());

if (container.Managed is МуТуре) {

(container.Managed as МуТуре).Method();

}

Добавленный код выделен жирным шрифтом. Это оператор if для проверки, явлтся ли тип, к которому обращается контейнер, типом мутуре. Несмотря на эту проверку, в коде отсутствуют указания, что необходимо делать в случае отрицельного результата этой проверки, т. е. когда свойство Managed не относится к ту мутуре. Необходимость выполнения проверки на обращение контейнера к прильному типу, а также в альтернативном плане действий в случае отрицательного результата проверки означает добавление довольно большого объема дополнельного  кода  к  первоначальному  коду.   Первоначальный  код  можно  сравнить с первой фразой  об  утках — скорее  всего,  мы  уловим  общий  смысл  сказанного, но вряд ли будем уверены в смысле услышанного на все 100%.

Теперь посмотрим на код, реализующий контейнер с помощью обобщений .NET:

public class GenericsContainer<ManagedType> { ManagedType _managed;

public GenericsContainer(ManagedType toManage) {

_managed = toManage;

}

public ManagedType Managed { get {

return „managed;

}

}

}

Код может либо использовать обобщения .NET, либо предоставлять типы, оснанные на обобщениях .NET. В определении класса GenericsContainer демонсируется код, предоставляющий типы, основанные на обобщениях .NET. Код, иользующий обобщения .NET, будет рассмотрен следующим.

Параметры обобщений .NET ассоциируются с типами, такими как классы и интеейсы, или же с методами. В случае класса Genericscontainer параметр обобщия ManagedType является типом.

ПРИМЕЧАНИЕ

Обычн о разработчики используют однобуквенные  идентификаторы  параметров  обоени я .NET. Я не являюсь сторонником такой нотации, т. к. она не  предоставляет абсолютн о никакой информации о параметре. Особенно это актуально в случае с нколькими параметрами. Я советую использовать идентификаторы, дающи е претавление о назначении и  работе  параметра,  а  в  конец  идентификатора  добавлять слово  туре, чтобы  указывать,  что определяется  параметр обобщени я  .NET.

В контейнере GenericsContainer объект ManagedType используется в качестве идеификатора вместо идентификатора object в типе container. Данное использование является практическим правилом. Всегда, когда объект используется общим образом для определения типа, скорее всего, можно применить обобщения .NET.

В следующем коде демонстрируется использование GenericsContainer С MyType.

GenericsContainer<MyType> container =

new GenericsContainer<МуТуре>(new MyType()); container.Managed.Method();

Обратите внимание на то, что при создании экземпляра GenericsContainer идеификатор ManagedType необходимо заменить идентификатором, представляющим существующий тип. Замена идентификатора называется конкретизацией типа обобщения .NET, в результате чего получается новый уникальный тип. Достоинсом обобщений .NET является то, что когда предоставлен конкретный тип, то нет необходимости проверять, что все правильно.

Среда исполнения .NET сгенерирует тип, чье назначение похоже на назначение, выраженное в следующем коде:

public class GenericsContainerMyType { MyType _managed;

public GenericsContainer(MyType toManage) {

„managed = toManage;

}

public MyType Managed { get {

return _managed;

}

}

}

Компилятор C# компилирует общий тип .NET в виде неполного типа. При конкризации  неполного типа  .NET создает полностью  новый тип,  не требуя для  этого

никаких специальных  действий со стороны разработчика. Это означает, что  если GenericsContainer используется с 15 разными типами, то при исполнении прраммы .NET сгенерирует 15 разных его определений.

АБСТРАКЦИИ     И     ОБОБЩЕНИЯ     .NET

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

.NET требует расширенных знаний о  структурах  программирования,  в  частности  зний,  как создавать  абстракции.

Обобщения .NET являются абстракциями. Точно так же, как интерфейсы являются абстракциями классов, обобщения .NET являются абстракциями над интерфейсами. Интерфейсы определяют намерения, а обобщения .NET — абстрактные реализации намерений.

Проблемным аспектом использования обобщений .NET является способность прраммиста собрать свои мысли в абстрактную реализацию намерений. Это сродни ставлению сочинения — написали один черновой вариант, перечитали его, внесли исправления, снова перечитали, снова исправили, и так до тех пор, пока не получим версию, наиболее близкую к желаемой. Используя обобщения  .NET,  мы  собираем мысли в кучу и формируем общий план действий.  Некоторым людям это трудно даея, и они не понимают обобщений .NET. Все, что требуется для создания кода с иользованием обобщений  .NET,  — это сначала хорошенько продумать будущий код.

Источник: Гросс  К. С# 2008:  Пер. с англ. — СПб.:  БХВ-Петербург, 2009. — 576 е.:  ил. — (Самоучитель)

По теме:

  • Комментарии