Главная страница

программирование. Руководство su P# a n Reference в herbert schildt полное руководство с 0 герберт шилдт


Скачать 3.32 Mb.
НазваниеРуководство su P# a n Reference в herbert schildt полное руководство с 0 герберт шилдт
Анкорпрограммирование
Дата25.01.2022
Размер3.32 Mb.
Формат файлаrtf
Имя файлаc-40-polnoe-rukovodstvo-2011.rtf
ТипРуководство
#341448
страница47 из 97
1   ...   43   44   45   46   47   48   49   50   ...   97

Тип переменной ob: System.String Значение: Тест на необобщенность

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

В этой программе обращает на себя внимание ряд любопытных моментов. Прежде всего, тип Т заменен везде, где он встречается в классе Non Gen. Благодаря этому в классе Non Gen может храниться объект любого типа, как и в обобщенном варианте этого класса. Но такой подход оказывается непригодным по двум причинам. Во‑первых, для извлечения хранящихся данных требуется явное приведение типов. И во‑вторых, многие ошибки несоответствия типов не могут быть обнаружены вплоть до момента выполнения программы. Рассмотрим каждую из этих причин более подробно.

Начнем со следующей строки кода.

int v = (int) iOb.GetObO;

Теперь возвращаемым типом метода GetOb () является object, а следовательно, для распаковки значения, возвращаемого методом GetOb () , и его последующего сохранения в переменной v требуется явное приведение к типу int. Если исключить приведение типов, программа не будет скомпилирована. В обобщенной версии этой программы приведение типов не требовалось, поскольку тип int указывался в качестве аргумента типа при создании объекта iOb. А в необобщенной версии этой программы потребовалось явное приведение типов. Но это не только неудобно, но и чревато ошибками.

А теперь рассмотрим следующую последовательность кода в конце анализируемой здесь программы.

// Этот код компилируется, но он принципиально неверный! iOb = strOb;

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

// v = (int) iOb.GetObO; // Ошибка при выполнении!

В этом коде значение переменной strOb присваивается переменной iOb. Но переменная strOb ссылается на объект, содержащий символьную строку, а не целое значение. Такое присваивание оказывается верным с точки зрения синтаксиса, поскольку все ссылки на объекты класса NonGen одинаковы, а значит, по ссылке на один объект класса NonGen можно обращаться к любому другому объекту класса NonGen. Тем не менее такое присваивание неверно с точки зрения семантики, как показывает следующая далее закомментированная строка кода. В этой строке тип, возвращаемый методом GetOb () , приводится к типу int, а затем предпринимается попытка присвоить полученное в итоге значение переменной int. К сожалению, в отсутствие обобщений компилятор не сможет выявить подобную ошибку. Вместо этого возникнет исключительная ситуация во время выполнения, когда будет предпринята попытка приведения к типу int. Для того чтобы убедиться в этом, удалите символы комментария в начале данной строки кода, скомпилируйте, а затем выполните программу. При ее выполнении возникнет ошибка.

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

компиляции, является главным преимуществом обобщений. Несмотря на то что в C# всегда имелась возможность создавать "обобщенный" код, используя ссылки на объекты, такой код не был типизированным, т.е. не обеспечивал типовую безопасность, а его неправильное применение могло привести к исключительным ситуациям во время выполнения. Подобные ситуации исключаются благодаря обобщениям. По существу, обобщения переводят ошибки при выполнении в разряд ошибок при компиляции. В этом и заключается основная польза от обобщений.

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

Console.WriteLine("Тип переменной ob: " + ob.GetType ());

Как пояснялось в главе 11, в классе object определен ряд методов, доступных для всех типов данных. Одним из них является метод GetType () , возвращающий объект класса Туре, который описывает тип вызывающего объекта во время выполнения. Следовательно, конкретный тип объекта, на который ссылается переменная ob, становится известным во время выполнения, несмотря на то, что тип переменной ob указан в исходном коде как object. Именно поэтому в среде CLR будет сгенерировано исключение при попытке выполнить неверное приведение типов во время выполнения программы.

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

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

// Простой обобщенный класс с двумя параметрами типа Т и V.

using System;

class TwoGenCT, V> {

T obi;

V ob2;

// Обратите внимание на то, что в этом конструкторе // указываются параметры типа Т и V. public TwoGen(Т ol, V о2)    {

obi = ol; оЬ2 = о2;

}

// Показать типы Т и V. public void showTypes() {

Console.WriteLine("К типу T относится " + typeof(Т));

Console.WriteLine("К типу V относится " + typeof(V));

}

return obi;

}

public V Get0bj2() { return ob2;

}

}

// Продемонстрировать применение обобщенного класса с двумя параметрами типа, class SimpGen {

static void Main() {

TwoGenCint, string> tgObj =

new TwoGenCint, string>(119, "Альфа Бета Гамма");

// Показать типы. tgObj.showTypes();

// Получить и вывести значения, int v = tgObj.getobl();

Console.WriteLine("Значение: " + v); string str = tgObj.GetObj2();

Console.WriteLine("Значение: " + str);

}

}

Эта программа дает следующий результат.

К типу Т относится System.Int32 К типу V относится System.String Значение: 119

Значение: Альфа Бета Гамма

Обратите внимание на то, как объявляется класс TwoGen.

class TwoGenCT, V> {

В этом объявлении указываются два параметра типа Т и V, разделенные запятой. А поскольку у класса TwoGen два параметра типа, то при создании объекта этого класса необходимо указывать два соответствующих аргумента типа, как показано ниже.

TwoGenCint, string> tgObj =

new TwoGenCint, string>(119, "Альфа Бета Гамма");

В данном случае вместо Т подставляется тип int, а вместо V – тип string.

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

TwoGencstring, string> х =

new TwoGencstring, string>("Hello", "Goodbye");

В этом случае оба типа, Т и V, заменяются одним и тем же типом, string. Ясно, что если бы аргументы были одного и того же типа, то два параметра типа бьГли бы не нужны.

Общая форма обобщенного класса

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

class имя_класса<список_параметров_типа> { // ...

А вот как выглядит синтаксис объявления ссылки на обобщенный класс.

имя_класса<список_аргументов_типа> имя_переменной =

new имя_класса<список_параметров_типа> ( список_аргументов_конструктора) ;

Ограниченные типы

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

class Gen {

Это означает, что вполне допустимо создавать объекты класса Gen, в которых тип Т заменяется типом int, double, string, FileStream или любым другим типом данных. Во многих случаях отсутствие ограничений на указание аргументов типа считается вполне приемлемым, но иногда оказывается полезно ограничить круг типов, которые могут быть указаны в качестве аргумента типа.

Допустим, что требуется создать метод, оперирующий содержимым потока, включая объекты типа FileStream или MemoryStream. На первый взгляд, такая ситуация идеально подходит для применения обобщений, но при этом нужно каким‑то образом гарантировать, что в качестве аргументов типа будут использованы только типы потоков, но не int или любой другой тип. Кроме того, необходимо как‑то уведомить компилятор о том, что методы, определяемые в классе потока, будут доступны для применения. Так, в обобщенном коде должно быть каким‑то образом известно, что в нем может быть вызван метод Read ().

Дл^ выхода из подобных ситуаций в C# предусмотрены ограниченные типы. Указывая параметр типа, можно наложить определенное ограничение на этот Параметр. Это делается с помощью оператора where при указании параметра типа:

class имя_класса<параметр_типа> where параметр_типа : ограничения { // ...

где ограничения указываются списком через запятую.

В C# предусмотрен ряд ограничений на типы данных.

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

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

•    Ограничение на конструктор, требующее предоставить конструктор без параметров в аргументе типа. Это ограничение накладывается с помощью оператора new ().

•    Ограничение ссылочного типа, требующее указывать аргумент ссылочного типа с помощью оператора class.

•    Ограничение типа значения , требующее указывать аргумент типа значения с помощью оператора struct.

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

Применение ограничения на базовый класс

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

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

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

where Т : имя_базового_класса

где Г обозначает имя параметра типа, а имя_базового_класса – конкретное имя ограничиваемого базового класса. Одновременно в этой форме ограничения может быть указан только один базовый класс.

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

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

using System;

class А {

public void Hello ()    {

Console.WriteLine("Hello");

// Класс В наследует класс А. class В : А { }

// Класс С не наследует класс А. class С '{ }

//В силу ограничения на базовый класс во всех аргументах типа,

// передаваемых классу Test, должен присутствовать базовый класс А. class Test where Т : А {

Т obj;

public Test(Т о) { obj = о;

}

public void SayHelloO {

// Метод Hello() вызывается, поскольку он объявлен в базовом классе А. obj.Hello();

}

}

class BaseClassConstraintDemo { static void Main() {

A a = new A();

В b = new В()

С с = new С()j

// Следующий код    вполне допустим,    поскольку    класс    А    указан    как    базовый.

Test tl = new    Test(a);

tl.SayHello();

// Следующий код    вполне допустим,    поскольку    класс    В    наследует    от    класса А.

Test t2 = new    Test (b);

t2.SayHello();

// Следующий код недопустим, поскольку класс С не наследует от класса А.

// Test t3 = new Test(c); // Ошибка!

// t3.SayHello(); // Ошибка!

}

}

В данном примере кода класс А наследуется классом В, но не наследуется классом С. Обратите также внимание на то, что в классе А объявляется метод Hello (), а класс Test объявляется как обобщенный следующим образом.

class Test where Т : А {

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

А теперь обратите внимание на то, что в классе Test объявляется метод SayHello (), как показано ниже.

public void SayHelloO {

// Метод Hello () вызывается, поскольку он объявлен в базовом классе А. obj.Hello();

}

Этот метод вызывает в свою очередь метод Hello () для объекта obj типа Т. Любопытно, что единственным основанием для вызова метода Hello () служит следующее требование ограничения на базовый класс: любой аргумент типа, привязанный к типу Т, должен относиться к классу А или
наследовать от класса А, в котором объявлен метод Hello () . Следовательно, любой допустимый тип Т будет также определять метод Hello () . Если бы данное ограничение на базовый класс не было наложено, то компилятору ничего не было бы известно о том, что метод Hello () может быть вызван для объекта типа Т. Убедитесь в этом сами, удалив оператор where из объявления обобщенного класса Test. В этом случае программа не подлежит компиляции, поскольку теперь метод Hello () неизвестен.

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

// Test t3 = new Test(c); // Ошибка!

// t3.SayHello(); // Ошибка!

Класс С не наследует от класса А, и поэтому он не может использоваться в качестве аргумента типа при создании объекта типа Test. Убедитесь в этом сами, удалив символы комментария и попытавшись перекомпилировать этот код.

Прежде чем продолжить изложение дальше, рассмотрим вкратце два последствия наложения ограничения на базовый класс. Во‑первых, это ограничение разрешает доступ к членам базового класса из обобщенного класса. И во‑вторых, оно гарантирует допустимость только тех аргументов типа, которые удовлетворяют данному ограничению, обеспечивая тем самым типовую безопасность.
1   ...   43   44   45   46   47   48   49   50   ...   97


написать администратору сайта