Определения обобщенных типов и конструируемые типы

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

Можно определить новый открытый тип через обобщенный, как показано в следующем примере:

1
2
3
4
5
6
7
8
public class MyClass<t>
{
private T innerObject;
}
public class Consumer<t>
{
private MyClass< Stack<t> > obj;
}

В этом случае определяется обобщенный тип Consumer, который содержит поле, основанное на другом обобщенном типе. При объявлении типа поля Consumer. obj тип MyClass< Stack > остается открытым, пока кто-нибудь не объявит конструируемый тип на основе Consumer<Т>, тем самым создав закрытый тип для содержащегося в нем поля.

Обобщенные классы и структуры

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

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

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

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

1
2
3
public class Container {}
public class Container<t> {}
public class Container<t, R> {}

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

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

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

Объявляя другой тип на основе определения обобщенного, вы объявляете то, что называется конструируемым типом, как показано ниже:

1
2
3
4
5
public class MyClass<t>
{
private Container<int> fieldl;
private Container<t> field2;
}

Оба поля в приведенном объявлении MyClass относятся к конструируемому типу, поскольку они объявляют новый тип на базе обобщенного типа Container. Тип Container является закрытым, поскольку все заданные аргументы типа сами являются закрытыми, т.е. он необобщенный.

Однако не каждый конструируемый тип является закрытым, fieldl является закрытым типом, в то время как field2 — открытым, поскольку его финальный тип должен быть определен во время выполнения на основе аргументов типа из MyClass.

В С# все идентификаторы объявлены и допустимы в пределах определенной области (контекста). В границах метода, например, любые локальные переменные, объявленные внутри фигурных скобок тела метода, доступны только в пределах этого метода. Аналогичные правила действуют по отношению к идентификаторам параметров-типов внутри обобщений. В предыдущем примере идентификатор Т действителен только в пределах области объявления класса.

Рассмотрим следующий пример вложенного класса:

1
2
3
4
5
public class MyClass<t>
{
public class MyNestedClass<r>
{
}

Идентификатор R действителен внутри контекста вложенного класса, и его нельзя использовать в объемлющем контексте объявления MyClass. Однако Т можно использовать во вложенном классе, поскольку вложенный класс определен внутри контекста, в котором идентификатор Т действителен. Обычно считается нежелательным скрывать идентификаторы внешних аргументов во вложенных контекстах, как и идентификаторы имен переменных внутри вложенных областей выполнения.

Например, попробуйте разобраться в показанном ниже запутанном коде:

1
2
3
4
5
6
7
8
9
10
public class MyClass<t> {
public class MyNestedClass<t>
{
}
private Containter<t> fieldl;
static void Main()
{
// Что это значит для MyNestedClass?
MyClass<int> closedTypelnstance = null;
}

Когда закрытый тип MyClass объявляется в Main, что это означает для вложенного типа? Ответ — ничего! Несмотря на то что объявление MyNestedClass использует тот же аргумент типа, оно не расширяется до такого:

1
2
3
4
5
6
7
// Этого НЕ происходит!
public class MyClass<int>
{
public class MyNestedClass<int> {
}
private Containter<int> fieldl;
}

Факт указания параметра типа для MyClass не означает также указание MyNestedClass. В действительности, аккуратнее описать результирующий MyClass следующим образом:

1
2
3
4
5
6
7
public class MyClass<int>
{
public class MyNestedClass<t>
{
}
private Containter<int> fieldl;
}

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

Лучше объявить его так:

1
2
3
4
5
6
7
8
9
public class MyClass<t> {
public class MyNestedClass<r> {
private T innerfieldl;
private R innerfield2;
}
private Containter<t> fieldl;
static void Main()  {
MyClass<int> closedTypelnstance = null;
}

Теперь в области определения MyNestedClass доступны оба параметра — Т и R. Здесь следует отметить один момент: несмотря на то, что объявлена переменная закрытого типа MyClass, это не подразумевает, что объявлены какие-то закрытые типы от MyNestedClass.

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

Например, если в MyClass объявлено статическое поле по имени MyValue, то закрытый тип MyClass имеет собственное статическое поле MyClass.MyValue, которое никак не связано со статическим полем MyClass.MyValue.

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

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

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

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

Обобщенные интерфейсы

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

Блестящим примером послужит IEnumerable.

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

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

Собственные типы коллекций должны наследоваться от типа Collection из пространства имен System.Collections.ObjectModel. Другие типы, такие как List, не предназначены для наследования, а должны использоваться в качестве низкоуровневого механизма хранения.

В Collection реализованы защищенные виртуальные методы, которые можно переопределить для настройки его поведения, в то время как в List такая возможность отсутствует.

Вы можете следить за любыми ответами на эту запись через 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