Сдам Сам

ПОЛЕЗНОЕ


КАТЕГОРИИ







Internal class Manager : Employee





{

Public sealed class Program

{

Public static void Main()

{

/* Создаем объект Manager и передаем его в PromoteEmployee. Manager ЯВЛЯЕТСЯ производным от Object, поэтому PromoteEmployee работает*/

Manager m = new Manager();

PromoteEmployee(m);

/* Создаем объект DateTime и передаем его в PromoteEmployee.

DateTime HE ЯВЛЯЕТСЯ производным от Employee,

поэтому PromoteEmployee генерирует исключение System.InvalidCastException */

DateTime newYears = new DateTime(2007, 1, 1);

PromoteEmployee(newYears);

}

Public static void PromoteEmployee(Object obj)

{

/* В этом месте компилятор не знает точно, на какой тип объекта ссылается obj, поэтому скомпилирует этот код. Однако в период выполнения CLR знает, на какой тип ссылается объект obj (приведение типа выполняется каждый раз), и проверяет, соответствует ли тип объекта типу Employee или другому типу, производному от Employee*/

Employee e = (Employee) obj;

}

}

 

Метод Main создает объект Manager и передает его в PromoteEmployee. Этот код компилируется и выполняется, так как тип Manager является производным от Object, на который рассчитан PromoteEmployee. Внутри PromoteEmployee CLR проверяет, на что ссылается obj — на объект Employee или объект типа, производного от Employee. Поскольку Manager — производный от Employee, CLR выполняет преобразование, и PromoteEmployee продолжает работу.

После того как PromoteEmployee возвращает управление, Main создает объект DateTime, который передает в PromoteEmployee. DateTime тоже является производным от Object, поэтому код, вызывающий PromoteEmployee, компилируется без проблем. Но при выполнении PromoteEmployee CLR выясняет, что obj ссылается на объект DateTime, не являющийся ни Employee, ни другим типом, производным от Employee. В этот момент CLR не в состоянии выполнить приведение типов и генерирует исключение SystemInvalidCastException.

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

Кстати, в данном примере было бы правильнее выбрать для метода PromoteEmployee в качестве типа параметра не Object, a Employee. Я же использовал Object, только чтобы показать, как обрабатывают операции приведения типов компилятор С# и CLR.

Приведение типов в С# с помощью операторов is и as

В С# есть другие механизмы приведения типов. Так, например, оператор is проверяет совместимость объекта с данным типом, а в качестве результата выдает значение типа Boolean: true или false. Оператор is никогда не генерирует исключение. Взгляните на код:

Object оbj = new Object();

Boolean b1 = (оbj is Object); // b1 равна true.

Boolean b2 = (оbj is Employee); // b2 равна false.

 

Если ссылка на объект равна null, оператор is всегда возвращает false, так как нет объекта, для которого нужно определить тип.

Обычно оператор is используется так:

If (оbj is Employee)

{

Employee emp = (Employee) obj;

// Используем еmp внутри оператора if

}

 

В этом коде CLR по сути проверяет тип объекта дважды: сначала в операторе is определяется совместимость obj с типом Employee, а затем в теле оператора if происходит анализ, является ли obj ссылкой на Employee. Контроль типов в CLR укрепляет безопасность, но при этом приходится жертвовать производительностью, так как CLR должна выяснять фактический тип объекта, на который ссылается переменная (obj), а затем проверить всю иерархию наследования на предмет наличия среди базовых типов заданного типа (Employee). Поскольку такая схема встречается в программировании часто, в С# предложен механизм, повышающий эффективность кода с помощью оператора as:

Employee emp = оbj as Employee;

if (emp!= null)

{

// Используем е внутри оператора if

}

 

В этом коде CLR проверяет совместимость obj с типом Employee, если это так, as возвращает ненулевой указатель на этот объект. Если obj и Employee несовместимы, оператор as возвращает null. Заметьте: оператор as заставляет CLR верифицировать тип объекта только раз, a if лишь сравнивает еmp с null — такая проверка намного эффективнее, чем определение типа объекта.

Оператор as отличается от приведения типа по сути только тем, что никогда не генерирует исключение. Если приведение типа невозможно, результатом является null. Если не сравнить полученный оператором результат с null и попытаться работать с пустой ссылкой, возникнет исключение NullReferenceException. Например, как показано здесь:

System.Object оbj = new Object(); // Создание объекта Object

Employee emp = obj as Employee; /* Приведение obj к типу Employee.

Преобразование невыполнимо: исключение не возникло, но

emp равно null*/

emp.ToString(); /* Обращение к emp вызывает исключение

NullReferenceException */

 

Пространства имен и сборки

Пространства имен позволяют объединять родственные типы в логические группы, в них проще найти нужный разработчику тип. Например, в пространстве имен System.Text описаны типы для обработки строк, а в пространстве имен System.IO — типы для выполнения операций ввода-вывода. В следующем коде создаются объекты System.IO.FileStream и System.Text.StringBuilder:

using System;

Public sealed class Program

{

Public static void Main()

{

System.IO.FileStream fs = new System.IO.FileStream(...);

System.Text.StringBuilder sb = new

System.Text.StringBuilder();

}

}

 

Этот код грешит многословием — он станет изящнее, если обращение к типам FileStream и StringBuilder будет компактнее. К счастью, многие компиляторы предоставляют программистам механизмы, позволяющие сократить объем набираемого текста. Так, в компиляторе С# предусмотрена директива using, а в Visual Basic — оператор Imports. Этот код аналогичен предыдущему:

using System.I0; // Попробуем избавиться от приставок "System.I0"

using System.Text; // Попробуем избавиться от приставок "System.Text"

Public sealed class Program

{

Public static void Main()

{

FileStream fs = new FileStream(...);

StringBuilder sb = new StringBuilder();

}

}

 

Для компилятора пространство имен — просто способ, позволяющий расширить имя типа и сделать его уникальным за счет добавления к началу имени групп символов, разделенных точками. Так, в нашем примере компилятор интерпретирует FileStream как System.IO.FileStream, a StringBuilder — как System.Text.StringBuilder.

Применять директиву using в С# и оператор Imports в Visual Basic не обязательно; можно набирать и полное имя типа. Директива using заставляет компилятор С# добавить к имени указанный префикс и «попытаться» найти подходящий тип.

В предыдущем примере компилятор должен гарантировать, что каждый упомянутый в коде тип существует и корректно обрабатывается: вызываемые методы существуют, число и типы передаваемых аргументов указаны правильно, значения, возвращаемые методами, обрабатываются надлежащим образом и т. д. Не найдя тип с заданным именем в исходных файлах и в перечисленных сборках, компилятор попытается добавить к имени типа приставку System.IO. и проверит, совпадает ли полученное имя с существующим типом. Если имя типа опять не обнаружено, он попробует повторить поиск уже с приставкой System.Text. Благодаря двум директивам using, показанным выше, я смог ограничиться именами FileStream и StringBuilder — компилятор автоматически расширит ссылки до System.IO.FileStream и System.Collections.StringBuilder. Полагаю, вам понятно, что вводить такой код намного проще, чем первоначальный.

Компилятору надо сообщить с помощью параметра /reference, в каких сборках искать описание типа. В поисках нужного типа компилятор просмотрит все известные ему сборки. Если подходящая сборка найдена, сведения о ней и типе помещаются в метаданные результирующего управляемого модуля. Чтобы информация из сборки была доступна компилятору, надо указать ему сборку, в которой описаны упоминаемые типы. По умолчанию компилятор С# автоматически просматривает сборку MSCorLib.dll, даже если она явно не указана. В ней содержатся описания всех фундаментальных FCL-типов, таких как Object, Int32, String и другие.

Легко догадаться, что такой способ обработки пространства имен чреват проблемами, если два (и более) типа с одинаковыми именами находятся в разных сборках. Microsoft настоятельно рекомендует при описании типов применять уникальные имена. Но порой это невозможно. В CLR поощряется повторное использование компонентов. Допустим, в приложении имеются компоненты, созданные в Microsoft и Wintellect, в которых есть типы с одинаковым названием, например Widget. В этом случае процесс формирования имен типов становится неуправляем, и, чтобы различать эти типы, придется указывать в коде их полные имена. При обращении к Widget от Microsoft надо указать MicrosoftWidget, а при ссылке на Widget от Wintellect — WintellectWidget. В следующем коде ссылка на Widget неоднозначна, и компилятор С# выдаст сообщение «error CS0104: 'Widget' is an ambiguous reference» («ошибка CS0104: 'Widget' — неоднозначная ссылка»):

using Microsoft; // Определяем приставку "Microsoft."

using Wintellect; // Определяем приставку "Wintellect."

Public sealed class Program

{

Public static void Main()

{

Widget w = new Widget(); // Неоднозначная ссылка

}

}

 

Чтобы избавиться от неоднозначности, надо явно указать компилятору, какой экземпляр Widget требуется создать:

using Microsoft; // Определяем приставку "Microsoft."

using Wintellect; // Определяем приставку "Wintellect."

Public sealed class Program

{

Public static void Main()

{

Wintellect.Widget w = new Wintellect.Widget(); /*

Неоднозначности нет*/

}

}

 

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

using Microsoft; // Определяем приставку "Microsoft."

using Wintellect; // Определяем приставку "Wintellect."

// Опишем символ WintellectWidget как псевдоним для Wintellect.Widget.

using WintellectWidget = Wintellect.Widget;

Public sealed class Program

{

Public static void Main()

{

WintellectWidget w = new WintellectWidgetQ; // Ошибки нет

}

}

 

Эти методы устранения неоднозначности хороши, но иногда их недостаточно. Представьте, что компании Australian Boomerang Company (ABC) и Alaskan Boat Corporation (ABC) создали каждая свой тип с именем BuyProduct и собираются поместить его в соответствующие сборки. Не исключено, что обе создадут пространства имен ABC, в которые и включат тип BuyProduct. Тот, кто намерен разработать приложение, оперирующее обоими типами, не сдвинется с места, если в языке программирования не окажется способа различать программными средствами не только пространства имен, но и сборки. К счастью в компиляторе С# поддерживается функция внешние псевдонимы (extern aliases), позволяющая справиться с такой проблемой. Внешние псевдонимы также позволяют обращаться к одному типу двух (или более) версий одной сборки. Подробнее о внешних псевдонимах см. спецификацию языка С#.

При проектировании типов, применяемых в библиотеках, которые могут использоваться третьими лицами, старайтесь описывать эти типы в пространстве имен так, чтобы компиляторы могли без труда преодолеть неоднозначность типов. Вероятность конфликта заметно снизится, если в названии пространства имен верхнего уровня указать полное, а не сокращенное имя компании. В документации.NET Framework SDK Microsoft использует пространство имен «Microsoft» для своих типов (к примеру, пространства имен Microsoft.CSharp, Microsoft.VisualBasic и Microsoft.Win32).

Чтобы создать пространство имен, достаточно ввести в код его объявление (на С#):

Namespace CompanyName

{

Public sealed class A

{ // TypeDef: CompanyName.A

}

Namespace X

{

public sealed class В {... } // TypeDef: CompanyName.X.В

}

}

 

В комментарии справа от объявления класса указано реальное имя типа, которое компилятор поместит в таблицу метаданных определения типов; это настоящее имя типа с точки зрения CLR.

Одни компиляторы вовсе не поддерживают пространства имен, а другие под термином «namespace» понимают нечто иное. В С# директива namespace инструктирует компилятор добавлять к каждому имени типа определенную приставку — это избавляет программиста от необходимости писать массу лишнего кода.







Что способствует осуществлению желаний? Стопроцентная, непоколебимая уверенность в своем...

Что вызывает тренды на фондовых и товарных рынках Объяснение теории грузового поезда Первые 17 лет моих рыночных исследований сводились к попыткам вычис­лить, когда этот...

ЧТО ТАКОЕ УВЕРЕННОЕ ПОВЕДЕНИЕ В МЕЖЛИЧНОСТНЫХ ОТНОШЕНИЯХ? Исторически существует три основных модели различий, существующих между...

Система охраняемых территорий в США Изучение особо охраняемых природных территорий(ООПТ) США представляет особый интерес по многим причинам...





Не нашли то, что искали? Воспользуйтесь поиском гугл на сайте:


©2015- 2024 zdamsam.ru Размещенные материалы защищены законодательством РФ.