class Gen<T> {
Т ob; // объявить переменную типа Т
// у конструктора имеется параметр типа Т.
public Gen(T о) {
ob = о;
}
// Возвратить переменную экземпляра ob, которая //относится к типу Т.
public T GetOb() {
return ob;
}
// Показать тип Т.
public void ShowTypeO {
Console.WriteLine("К типу Т относится " + typeof(T));
}
}
// Применение обобщенного класса,
class GenericsDemo {
static void Main() {
//Создать переменную ссылки на объект Gen типа int.
Gen<int> iOb;
//Создать объект типа Gen<int> и присвоить ссылку на //него переменной iOb.
iOb = new Gen<int>(102);
//Показать тип данных, хранящихся в переменной iOb.
iOb.ShowType();
// Получить значение переменной iOb.
int v = iOb.GetOb() ;
Console.WriteLine("Значение: " + v) ;
Console.WriteLine();
// Создать объект типа Gen для строк.
Gen<string> strOb = new Gen<string>("Строка.");
//Показать тип данных, хранящихся в переменной //strOb.
strOb.ShowType();
// Получить значение переменной strOb.
string str = strOb.GetOb();
Console.WriteLine("Значение: " + str);
}
Когда для класса указывается аргумент типа, например int или string, то создается так называемый в С# закрыто сконструированный тип. В частности, Gen<int> является закрыто сконструированным типом. Такой обобщенный тип, как Gen<T>, является абстракцией. И только после того, как будет сконструирован конкретный вариант, например Gen<int>, создается конкретный тип. А конструкция, подобная Gen<T>, называется в С# открыто сконструированным типом, поскольку в ней указывается параметр типа Т, но не такой конкретный тип, как int.
В С# чаще определяются такие понятия, как открытый и закрытый типы. Открытым типом считается такой параметр типа или любой обобщенный тип, для которого аргумент типа является параметром типа или же включает его в себя. А любой тип, не относящийся к открытому, считается закрытым. Сконструированным типом считается такой обобщенный тип, для которого предоставлены все аргументы типов. Если все эти аргументы относятся к закрытым типам, то такой тип считается закрыто сконструированным. А если один или несколько аргументов типа относятся к открытым типам, то такой тип считается открыто сконструированным.
Следует иметь в виду, что ссылка на один конкретный вариант обобщенного типа не совпадает по типу с другим вариантом того же самого обобщенного типа. Так, если ввести в приведенный выше пример следующую строку кода, то она не будет скомпилирована:
iOb = strOb; // Неверно!
Несмотря на то что обе переменные, iOb и strOb, относятся к типу Gen<T>, они ссылаются на разные типы, поскольку у них разные аргументы.
В С# также предусмотрены ограниченные типы. Указывая параметр типа, можно наложить определенное ограничение на этот параметр. Это делается с помощью оператора where при указании параметра типа:
class имя_класса<параметр_типа> where параметр_типа : ограничения { // ...
где ограничения указываются списком через запятую.
В С# предусмотрен ряд ограничений на типы данных:
1) Ограничение на базовый класс, требующее наличия определенного базового класса в аргументе типа. Это ограничение накладывается указанием имени требуемого базового класса. Разновидностью этого ограничения является неприкрытое ограничение типа, при котором на базовый класс указывает параметр типа, а не конкретный тип. Благодаря этому устанавливается взаимосвязь между двумя параметрами типа.
2) Ограничение на интерфейс, требующее реализации одного или нескольких интерфейсов аргументом типа. Это ограничение накладывается указанием имени требуемого интерфейса.
3) Ограничение на конструктор, требующее предоставить конструктор без параметров в аргументе типа. Это ограничение накладывается с помощью оператора new ().
4) Ограничение ссылочного типа, требующее указывать аргумент ссылочного типа с помощью оператора class.
5) Ограничение типа значения, требующее указывать аргумент типа значения с помощью оператора struct.
В С# разрешается создавать обобщенные структуры. При этом синтаксис для них применяется такой же, как и для обобщенных классов.
В методах, объявляемых в обобщенных классах, может использоваться параметр типа из данного класса, а следовательно, такие методы автоматически становятся обобщенными по отношению к параметру типа. Но помимо этого имеется возможность объявить обобщенный метод со своими собственными параметрами типа и даже создать обобщенный метод, заключенный в необобщенном классе.
Ниже приведена общая форма объявления обобщенного метода:
возвращаемый_тип имя_метода<список_параметров_типа> (список_параметров) {// ...
список_параметров_типа обозначает разделяемый запятой список параметров типа. В объявлении обобщенного метода список параметров типа следует после имени метода.
Помимо обобщенных классов и методов, в С# допускаются обобщенные интерфейсы. Такие интерфейсы указываются аналогично обобщенным классам.
В С# коллекция представляет собой совокупность объектов. В среде .NET Framework имеется немало интерфейсов и классов, в которых определяются и реализуются различные типы коллекций. Коллекции упрощают решение многих задач программирования благодаря тому, что предлагают готовые решения для создания целого ряда типичных, но порой трудоемких для разработки структур данных. Например, в среду .NET Framework встроены коллекции, предназначенные для поддержки динамических массивов, связных списков, стеков, очередей и хеш-таблиц.
Главное преимущество коллекций заключается в том, что они стандартизируют обработку групп объектов в программе. Все коллекции разработаны на основе набора четко определенных интерфейсов.
В среде .NET Framework поддерживаются пять типов коллекций:
а) необобщенные;
б) специальные;
в) с поразрядной организацией;
г) обобщенные;
д) параллельные.
Необобщенные коллекции реализуют ряд основных структур данных, включая динамический массив, стек, очередь, а также словари, в которых можно хранить пары "ключ-значение". В отношении необобщенных коллекций важно иметь в виду следующее: они оперируют данными типа object. Таким образом, необобщенные коллекции могут служить для хранения данных любого типа, причем в одной коллекции допускается наличие разнотипных данных. Очевидно, что такие коллекции не типизированы, поскольку в них хранятся ссылки на данные типа object. Классы и интерфейсы необобщенных коллекций находятся в пространстве имен System.Collections.
Специальные коллекции оперируют данными конкретного типа или же делают это каким-то особым образом. Например, имеются специальные коллекции для символьных строк, а также специальные коллекции, в которых используется однонаправленный список. Специальные коллекции объявляются в пространстве имен System.Collections.Specialized.
В прикладном интерфейсе Collections API определена одна коллекция с поразрядной организацией — это BitArray. Коллекция типа BitArray поддерживает поразрядные операции, т.е. операции над отдельными двоичными разрядами, например И или исключающее ИЛИ, а следовательно, она существенно отличается своими возможностями от остальных типов коллекций. Коллекция типа BitArray объявляется в пространстве имен System. Collections.
Обобщенные коллекции обеспечивают обобщенную реализацию нескольких стандартных структур данных, включая связные списки, стеки, очереди и словари. Такие коллекции являются типизированными в силу их обобщенного характера. Это означает, что в обобщенной коллекции могут храниться только такие элементы данных, которые совместимы по типу с данной коллекцией. Благодаря этому исключается случайное несовпадение типов. Обобщенные коллекции объявляются в пространстве имен System.Collections.Generic.
Параллельные коллекции поддерживают многопоточный доступ к коллекции. Это обобщенные коллекции, определенные в пространстве имен System.Collections.Concurrent.
В пространстве имен System.Collections.ObjectModel находится также ряд классов, поддерживающих создание пользователями собственных обобщенных коллекций.
Основополагающим для всех коллекций является понятие перечислителя, который поддерживается в необобщенных интерфейсах IEnumerator и IEnumerable, а также в обобщенных интерфейсах IEnumerator<T> и IEnumerable<T>. Перечислитель обеспечивает стандартный способ поочередного доступа к элементам коллекции. Следовательно, он перечисляет содержимое коллекции. В каждой коллекции должна быть реализована обобщенная или необобщенная форма интерфейса IEnumerable, поэтому элементы любого класса коллекции должны быть доступны посредством методов, определенных в интерфейсе IEnumerator или IEnumerator<T>. Это означает, что, внеся минимальные изменения в код циклического обращения к коллекции одного типа, его можно использовать для аналогичного обращения к коллекции другого типа.
С перечислителем непосредственно связано другое средство, называемое итератором. Это средство упрощает процесс создания классов коллекций, например, специальных, поочередное обращение к которым организуется в цикле for each.
В пространстве имен System.Collections определен целый ряд интерфейсов необобщенных коллекций. Начинать рассмотрение необобщенных коллекций следует именно с интерфейсов, поскольку они определяют функциональные возможности, которые являются общими для всех классов необобщенных коллекций. Интерфейсы, служащие опорой для необобщенных коллекций, сведены в табл. 5.
Таблица 5
Интерфейсы необобщенных коллекций
Интерфейс |
Описание |
iCollection |
Определяет элементы, которые должны иметь все необобщенные коллекции |
iComparer |
Определяет метод Compare () для сравнения объектов, хранящихся в коллекции |
iDictionary
|
Определяет коллекцию, состоящую из пар "ключ-значение" |
IDictionaryEnumerator |
Определяет перечислитель для коллекции, реализующей интерфейс IDictionary |
IEnumerable |
Определяет метод GetEnumerator(), предоставляющий перечислитель для любого класса коллекции |
IEnumerator |
Предоставляет методы, позволяющие получать содержимое коллекции по очереди |
iEqualityComparer |
Сравнивает два объекта на предмет равенства |
iList |
Определяет коллекцию, доступ к которой можно получить с помощью индексатора |
iStructuralComparable |
Определяет метод CompareTo (), применяемый для структурного сравнения |
iStructuralEquatable |
Определяет метод Equals(), применяемый для выяснения структурного, а не ссылочного равенства. Кроме того, определяет метод GetHashCode () |
Далее будут рассмотрены интерфейсы ICollection и IDictionary.
Интерфейс ICollection служит основанием, на котором построены все необобщенные коллекции. В нем объявляются основные методы и свойства для всех необобщенных коллекций. Он также наследует от интерфейса IEnumerable.
В интерфейсе ICollection определяются перечисленные ниже свойства. Свойство Count используется чаще всего, поскольку оно содержит количество элементов, хранящихся в коллекции на данный момент. Если значение свойства Count равно нулю, то коллекция считается пустой.
В интерфейсе ICollection определяется следующий метод.
void CopyTo(Array target, int startIdx)
Метод СоруТо() копирует содержимое коллекции в массив target, начиная с элемента, указываемого по индексу startIdx. Следовательно, метод СоруТо() обеспечивает в С# переход от коллекции к стандартному массиву.
Благодаря тому что интерфейс ICollection наследует от интерфейса IEnumerable, в его состав входит также единственный метод, определенный в интерфейсе IEnumerable. Это метод GetEnumerator (), объявляемый следующим образом.
IEnumerator GetEnumerator()
Он возвращает перечислитель для коллекции.
В интерфейсе IList объявляется такое поведение необобщенной коллекции, которое позволяет осуществлять доступ к ее элементам по индексу с отсчетом от нуля.
В интерфейсе IDictionary определяется такое поведение необобщенной коллекции, которое позволяет преобразовать уникальные ключи в соответствующие значения. Ключ представляет собой объект, с помощью которого значение извлекается впоследствии. Следовательно, в коллекции, реализующей интерфейс IDictionary, хранятся пары "ключ-значение". Как только подобная пара будет сохранена, ее можно извлечь с помощью ключа. Интерфейс IDictionary наследует от интерфейсов ICollection и IEnumerable. Некоторые из методов, объявленных в IDictionary, генерируют исключение ArgumentNullException при попытке указать пустой ключ, поскольку пустые ключи не допускаются.
Методы, определенные в IDictionary:
1) void Add(object key, object value) - добавляет в вызывающую коллекцию пару "ключ-значение", определяемую параметрами key и value.
2) void Clear() - удаляет все пары "ключ-значение" из вызывающей коллекции.
3) bool Contains(object key) - возвращает логическое значение true, если вызывающая коллекция содержит объект key в качестве ключа, в противном случае — логическое значение false.
4) iDictionaryEnumerator GetEnumerator() - возвращает перечислитель для вызывающей коллекции.
5) void Remove (object key) - удаляет из коллекции элемент, ключ которого равен значению параметра key.
Свойства, определенные в IDictionary:
1) bool isFixedSize { get; } - принимает логическое значение true, если словарь имеет фиксированный размер;
2) bool isReadOnly{ get; } - принимает логическое значение true, если словарь доступен только для чтения;
3) ICollection Keys { get; } - получает коллекцию ключей;
4) ICollection Values { get; } - получает коллекцию значений.
Кроме того, в интерфейсе IDictionary определяется следующий индексатор.
object this[object key] { get; set; }
Этот индексатор служит для получения и установки значения элемента коллекции, а также для добавления в коллекцию нового элемента. Но в качестве индекса в данном случае служит ключ элемента, а не собственно индекс.
Пример:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
public class MainClass
{
public static void Main()
{
IDictionary<string, float> salaryMap = new
Dictionary<string, float>();
salaryMap.Add("S", 60.5);
salaryMap.Add("W", 10.0);
salaryMap.Add("J", 30.99);
Console.WriteLine(salaryMap.ContainsKey("S"));
Console.WriteLine(salaryMap.ContainsKey("T"));
}
}
Аббревиатура LINQ означает Language-Integrated Query, т.е. язык интегрированных запросов. Это понятие охватывает ряд средств, позволяющих извлекать информацию из источника данных. Извлечение данных составляет важную часть многих программ. Например, программа может получать информацию из списка заказчиков, искать информацию в каталоге продукции или получать доступ к учетному документу, заведенному на работника. Как правило, такая информация хранится в базе данных, существующей отдельно от приложения. В прошлом для взаимодействия с такой базой данных приходилось формировать запросы на языке структурированных запросов (SQL). А для доступа к другим источникам данных, например в формате XML, требовался отдельный подход.
LINQ дополняет С# средствами, позволяющими формировать запросы для любого LINQ-совместимого источника данных. При этом синтаксис, используемый для формирования запросов, остается неизменным, независимо от типа источника данных. Возможности формировать запросы отныне полностью интегрированы в язык С#.
В основу LINQ положено понятие запроса, в котором определяется информация, получаемая из источника данных.
Как только запрос будет сформирован, его можно выполнить. Это делается, в частности, в цикле foreach. В результате выполнения запроса выводятся его результаты. Поэтому использование запроса может быть разделено на две главные стадии. На первой стадии запрос формируется, а на второй — выполняется. Таким образом, при формировании запроса определяется, что именно следует извлечь из источника данных. А при выполнении запроса выводятся конкретные результаты.