C# Lect

.NET Framework & C#
13 TOC \o "1-3" \u 14.NET Framework & C# 13 PAGEREF _Toc480886131 \h 14115
Введение 13 PAGEREF _Toc480886132 \h 14315
1. Обзор технологии .NET и C# 13 PAGEREF _Toc480886133 \h 14315
2. Характеристика платформы .NET Framework 13 PAGEREF _Toc480886134 \h 14515
2.1. Состав .NET Framework 13 PAGEREF _Toc480886135 \h 14515
2.2. Процесс разработки приложений на платформе CLR 13 PAGEREF _Toc480886136 \h 141115
2.3. Основные достоинства CLR и FCL 13 PAGEREF _Toc480886137 \h 141115
3. Примеры программ 13 PAGEREF _Toc480886138 \h 141515
3.1. Пример консольного приложения 13 PAGEREF _Toc480886139 \h 141515
3.2. Класс стек на основе связного списка 13 PAGEREF _Toc480886140 \h 141615
3.3. Шаблонный класс список 13 PAGEREF _Toc480886141 \h 141615
3.4. Класс Environment: получение сведений о компьютере 13 PAGEREF _Toc480886142 \h 142215
3.5. Чтение текстового файла 13 PAGEREF _Toc480886143 \h 142315
4. Основы типов 13 PAGEREF _Toc480886148 \h 142615
4.1. Все типы производные от System.Object 13 PAGEREF _Toc480886149 \h 142615
4.2. Приведение типов 13 PAGEREF _Toc480886150 \h 142715
4.3. Приведение типов в С# с помощью операторов is и as 13 PAGEREF _Toc480886151 \h 142915
4.4. Пространства имен и сборки 13 PAGEREF _Toc480886152 \h 143015
4.5. Как связаны пространства имен и сборки 13 PAGEREF _Toc480886153 \h 143315
5. Элементарные, ссылочные и значимые типы 13 PAGEREF _Toc480886154 \h 143415
5.1. Элементарные типы в языках программирования 13 PAGEREF _Toc480886155 \h 143415
5.2. Проверяемые и непроверяемые операции для элементарных типов 13 PAGEREF _Toc480886156 \h 143715
5.3. Ссылочные и значимые типы 13 PAGEREF _Toc480886157 \h 144015
6. Основные сведения о членах и типах 13 PAGEREF _Toc480886158 \h 144415
6.1. Члены типа 13 PAGEREF _Toc480886159 \h 144415
6.2. Видимость типа 13 PAGEREF _Toc480886160 \h 144715
6.3. Доступ к членам 13 PAGEREF _Toc480886161 \h 144715
6.4. Статические классы 13 PAGEREF _Toc480886162 \h 144815
6.5. Частичные классы, структуры и интерфейсы 13 PAGEREF _Toc480886163 \h 145015
6.6. Компоненты, полиморфизм и версии 13 PAGEREF _Toc480886164 \h 145015
6.7. Вызов виртуальных методов, свойств и событий в CLR 13 PAGEREF _Toc480886165 \h 145215
7. Константы и поля 13 PAGEREF _Toc480886166 \h 145515
7.1. Константы 13 PAGEREF _Toc480886167 \h 145515
7.2. Поля 13 PAGEREF _Toc480886168 \h 145615
8. Методы: конструкторы, операторы, преобразования и параметры 13 PAGEREF _Toc480886169 \h 145915
8.1. Конструкторы экземпляров и классы (ссылочные типы) 13 PAGEREF _Toc480886170 \h 145915
8.2. Конструкторы экземпляров и структуры (значимые типы) 13 PAGEREF _Toc480886171 \h 146115
8.3. Конструкторы типов 13 PAGEREF _Toc480886172 \h 146415
8.4. Методы перегруженных операторов 13 PAGEREF _Toc480886173 \h 146715
9. Свойства 13 PAGEREF _Toc480886174 \h 146915
9.1. Свойства без параметров 13 PAGEREF _Toc480886175 \h 146915
9.2. Свойства с параметрами 13 PAGEREF _Toc480886176 \h 147215
9.3. Выбор главного свойства с параметрами 13 PAGEREF _Toc480886177 \h 147515
10. Словарь терминов 13 PAGEREF _Toc480886178 \h 147715
Список литературы по языку С++ и программированию для Windows 13 PAGEREF _Toc480886179 \h 148315
Список литературы по платформе .NET 13 PAGEREF _Toc480886180 \h 148415
15
Введение
В данной работе очень широко использован материал из великолепной работы Д.Рихтера [11].
Обзор технологии .NET и C#
За прошедшие годы Microsoft выпустила массу технологий, призванных облегчить создание архитектуры и реализацию исходного кода приложений. Многие технологии предусматривают абстрагирование, которое позволяет разработчикам сосредоточиться на решении предметных задач, меньше думая об особенностях аппаратного обеспечения и операционных систем. Вот всего лишь несколько примеров таких технологий.
Библиотека Microsoft Foundation Class (MFC) уровень абстрагирования, служащий в языке C++ для программирования графического пользовательского интерфейса. Используя MFC, разработчики могут больше внимания уделить самой программе и меньше заниматься циклами обработки сообщений, оконными процедурами, классами окон и т. п.
Microsoft Visual Basic 6 и более ранние версии служили разработчикам абстракцией, облегчающей создание приложений с графическим пользовательским интерфейсом. Эта технология абстрагирования служила практически тем же целям, что и MFC, но ориентировалась на программистов, пишущих на Basic, требовался другой подход к различным аспектам программирования графического интерфейса.
Active Server Pages (ASP) служила для абстрагирования при создании активных и динамических Web-сайтов с использованием Visual Basic Script или JScript. Она позволила разработчикам абстрагироваться от особенностей сетевых взаимодействий и больше внимания уделять содержанию Web-страниц.
Библиотека Active Template Library (ATL) уровень абстрагирования, облегчающий создание компонентов, которые доступны для использования специалистами, работающими с различными языками программирования.

Как видите, все эти технологии абстрагирования создавались, чтобы разработчики могли забыть о технических деталях и сосредоточиться на конкретных вещах, будь то приложения с графическим пользовательским интерфейсом, Web-приложения или компоненты. Если требовалось создать Web-сайт, на котором использовался определенный компонент, разработчику приходилось осваивать несколько технологий: ASP и ATL. Более того, нужно было знать многие языки программирования, так как для ASP требовался Visual Basic Script или JScript, а для ATL C++. Несмотря на то, что эти технологии значительно облегчали работу, они требовали от программиста осваивать массу материала. Часто случалось так, что различные технологии разрабатывались без расчета на совместное использование, и разработчики сталкивались с необходимостью решать непростые проблемы интеграции.
Другая цель .NET Framework предоставить разработчикам возможность создавать код на любимом языке по собственному выбору. Теперь можно создать и Web-сайт, и его компоненты, используя один язык, например Visual Basic или сравнительно новый, предлагаемый Microsoft язык С#.
Единая модель программирования, API-интерфейс и язык программирования большой шаг вперед в области технологий абстрагирования и огромная помощь разработчикам в их нелегкой работе. Но это далеко не все все эти функции означают, что в прошлом остались проблемы интеграции, что значительно упростило тестирование, развертывание, администрирование, управление версиями и повторное использование и переориентацию кода на выполнение других задач.
Уже несколько лет я (Д.Рихтер) использую .NET Framework и должен сказать с уверенностью, что ни за что не вернусь к устаревшим технологиям абстрагирования и способам разработки ПО. И, если меня заставят, я предпочту сменить профессию! Вот как трудно отвыкать от хорошего. Честно говоря, вспоминая, чего стоило создавать приложения с использованием старых технологий, я просто не могу представить, как разработчикам вообще удавалось так долго создавать работающее ПО.
Характеристика платформы .NET Framework
Состав .NET Framework

.NET Framework состоит из двух частей: общеязыковой исполняющей среды (common language runtime, CLR) и библиотеки классов (Framework Class Library, FCL). CLR предоставляет модель программирования, используемую во всех типах приложений. У CLR собственный загрузчик файлов, диспетчер памяти (сборщик мусора), система безопасности (безопасность доступа к коду), пул потоков и другое. Кроме того, CLR предоставляет объектно-ориентированную модель программирования, определяющую, как выглядят и ведут себя типы и объекты.


Рис.1.1. Структура .NET Framework

Base Class Library (BCL)

BCL стандартная библиотека классов платформы «.NET Framework». Программы, написанные на любом из языков, поддерживающих платформу .NET, могут пользоваться классами и методами BCL создавать объекты классов, вызывать их методы, наследовать необходимые классы BCL и т. д.
В отличие от многих других библиотек классов, например, MFC, ATL/WTL или SmartWin, библиотека BCL не является некоей «надстройкой» над функциями операционной системы или над каким-либо API. Библиотеки BCL является органической частью самой платформы .NET Framework, её «родным» API. Её можно рассматривать как API виртуальной машины .NET. BCL обновляется с каждой версией .NET Framework.

В BCL объявлено около 30 пространств имён, вот некоторые из них:
System
Наиболее важное пространство имён. Включает в себя все примитивные типы языка C#: «пустой» тип Void, знаковые и беззнаковые целочисленные типы (например, Int32), типы чисел с плавающей запятой одинарной и двойной точности (Single, Double), «финансовый» тип Decimal, логический тип Boolean, символьный и строковый типы Char и String, а также, например, тип DateTime и другие. Обеспечивает также необходимым набором инструментов для работы с консолью, математическими функциями, и базовыми классами для атрибутов, исключений и массивов.
System.Collections
Определяет множество общих контейнеров или коллекций, используемых в программировании такие как список, очередь, стек, хеш-таблица и некоторые другие. Также включена поддержка обобщений (Generics).
System.Drawing
Предоставляет доступ к GDI+, включая поддержку для 2D растровой и векторной графики, изображений, печати и работы с текстом.
System.Text
Поддерживает различные кодировки, регулярные выражения, и другие полезные механизмы для работы со строками(класс StringBuilder).
System.Threading
Облегчает мультипотоковое программирование.
System.Timers
Позволяет вызвать событие через определённый интервал времени.

Framework Class Library

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



Рис. 1.2 Состав FCL

Windows Presentation Foundation

Windows Presentation Foundation (WPF) система для построения клиентских приложений Windows с визуально привлекательными возможностями взаимодействия с пользователем. Это графическая (презентационная) подсистема в составе .NET Framework (начиная с версии 3.0), использующая язык XAML. С помощью WPF можно создавать широкий спектр как автономных, так и запускаемых в браузере приложений.
В основе WPF лежит векторная система визуализации, не зависящая от разрешения устройства вывода и созданная с учётом возможностей современного графического оборудования. WPF предоставляет средства для создания визуального интерфейса, включая язык XAML (Extensible Application Markup Language), элементы управления, привязку данных, макеты, двухмерную и трёхмерную графику, анимацию, стили, шаблоны, документы, текст, мультимедиа и оформление.
Графической технологией, лежащей в основе WPF, является DirectX, в отличие от Windows Forms, где используется GDI/GDI+. Производительность WPF выше, чем у GDI+ за счёт использования аппаратного ускорения графики через DirectX.
Также существует урезанная версия CLR, называющаяся WPF/E, она же известна как Silverlight.
XAML представляет собой язык декларативного описания интерфейса, основанный на XML. Также реализована модель разделения кода и дизайна, позволяющая кооперироваться программисту и дизайнеру. Кроме того, есть встроенная поддержка стилей элементов, а сами элементы легко разделить на элементы управления второго уровня, которые, в свою очередь, разделяются до уровня векторных фигур и свойств/действий. Это позволяет легко задать стиль для любого элемента, например, Button (кнопка).
Средства разработки. Для работы с WPF требуется любой .NET-совместимый язык. В этот список входит множество языков: C#, VB, C++/CLI, F#, Ruby, Python, Delphi (Prism), Lua и многие другие. Для полноценной работы может быть использована как Visual Studio, так и Expression Blend. Первая ориентирована на программирование, а вторая на дизайн и позволяет делать многие вещи, не прибегая к ручному редактированию XAML. Примеры этому анимация, стилизация, состояния, создание элементов управления и так далее.

Delphi Prism 2011 – кросс-платформенная среда разработки, к компиляторам которой прилагается и IDE (MonoDevelop), способная функционировать под управлением Windows, Linux и Mac OS X. При использовании Visual Studio 2010 в качестве IDE поддерживаются визуальные дизайнеры для приложений Windows Forms, WPF, Silverlight и ASP.NET. MonoDevelop в этом отношении гораздо ограниченнее – в дополнение к визуальному дизайнеру ASP.NET предоставляется только возможность дизайна форм в редакторе GTK#, который, помимо прочего, недоступен на Mac OS X. Таким образом, несмотря на наличие в комплекте поставки кросс-платформенной IDE, паритет по функциональности на разных платформах все еще отсутствует.Тем не менее, Delphi Prism вполне может занять нишу удобных кросс-платформенных средств разработки, потребность в которых велика. Особой конкуренции здесь пока не наблюдается, но все же имеется своя специфика: многие довольно качественные продукты совершенно бесплатны, например тот же MonoDevelop для C# или IDE для Java, такие как NetBeans и Eclipse. На фоне этого Delphi Prism с не очень популярным языком программирования и довольно высокой ценой выглядит недостаточно конкурентоспособно. Ситуацию может исправить поддержка каких-нибудь уникальных технологий, но ни о чем подобном Embarcadero не заявляла, следовательно, в ближайшее время Delphi Prism остается узконаправленным продуктом, ориентированным на разработчиков, использующих Object Pascal и заинтересованных в кросс-платформенности своих решений.


Windows Communication Foundation

WCF программная среда, используемая для обмена данными между приложениями, входящих в состав .NET Framework. До своего выпуска в декабре 2006 года в составе .NET Framework 3.0, WCF был известен под кодовым именем Indigo.
WCF делает возможным построение безопасных и надёжных транзакционных систем через упрощённую унифицированную программную модель межплатформенного взаимодействия. Комбинируя функциональность существующих технологий .NET по разработке распределённых приложений (ASP.NET XML Web Services ASMX, WSE 3.0, .NET Remoting, .NET Enterprise Services и System.Messaging), WCF предоставляет единую инфраструктуру разработки, при умелом применении повышающую производительность и снижающую затраты на создание безопасных, надёжных и транзакционных Web-служб нового поколения. Заложенные в неё принципы интероперабельности позволяют организовать работу с другими платформами, для чего используются технологии взаимодействия платформ, например WSIT, разрабатываемые на базе открытого исходного кода.

Класс службы WCF не может существовать самостоятельно. Каждая служба WCF должна находиться под управлением некоторого процесса Windows, называемого хостовым процессом. Существуют несколько вариантов хостинга:
Автохостинг (то есть хост-процессом является, к примеру, консольное или графическое Windows приложение)
Хостинг в одной из служб Windows
Хостинг с использованием IIS (Internet Information Server) или WAS (Windows Activation Services)

Windows Workflow Foundation
WWF или WF уже не новая технология компании Microsoft, разработанная для создания и выполнения потоков работ (Workflow). Однако на данный момент она используется не очень активно, а многие разработчики вообще не слышали про нее.
Ключевым понятием в WF является Активность (Activity) класс, выполняющий единицу работы в среде выполнения WF. Термины Поток работ и Активность являются синонимами в контексте WF. Каждая Активность выполняет какое-либо действие буквально программный код (например, на языке C#). Активности имеют входные и выходные параметры, переменные. Активность может представлять собой композицию из нескольких дочерних Активностей, в таком случае в процессе работы родительская Активность управляет запуском своих дочерних элементов в среде выполнения в соответствии со своей внутренней логикой. Например, Активность Parallel из базовой библиотеки Активностей (входит в поставку .NET Framework) запускает дочерние элементы параллельно. А поток работ if, как не сложно догадаться из названия, запускает один из двух дочерних элементов в зависимости от результата проверки заданного условия.

Таким образом, в конечном итоге создание потока работ обычно сводится к составлению в дизайнере блок-схемы на основе Активностей базовой библиотеки в сочетании с Активностями собственной разработки. Поток работ, построенный в дизайнере, кодируется на языке XAML (расширение XML).
Неплохое понятие WF см. по адресу [ Cкачайте файл, чтобы посмотреть ссылку ]

Card Space
Windows CardSpace ныне отмененное клиентское ПО с патентованной технологией единого входа от Microsoft. WCS это способ идентификации пользователей при перемещении между ресурсами Интернета без необходимости повторного ввода имен и паролей.
В отличие от ранее используемых технологий унифицированной идентификации (например, Microsoft Passport) WCS управляет непосредственно пользователями и приложениями, с которыми устанавливается контакт (а не из централизованного ресурса). Можно применять разные схемы и уровни сложности для идентификации при доступе на Web-форумы и для банковских операций.
15 февраля 2011 корпорация Майкрософт объявила об отмене Windows CardSpace 2.0 и о работе над замещающим ПО U-Prove.



Language Integrated Query (LINQ)

Language-Integrated Query (LINQ) представляет собой набор функций, введенных в версии Visual Studio 2008, который расширяет мощные возможности запросов, поддерживаемых синтаксисов языков C# и Visual Basic. LINQ предоставляет стандартные, легкоизучаемые шаблоны для выполнения запросов и обновления данных и эта технология может быть расширена для поддержки потенциально любого типа хранилища данных. Visual Studio включает в себя LINQ сборки поставщика, позволяющие использовать LINQ с .NET Framework коллекций, баз данных SQL Server, ADO.NET Datasets и XML-документов.

Task Parallel Library (TPL)
Источник: [ Cкачайте файл, чтобы посмотреть ссылку ]
Библиотека параллельных задач (TPL) представляет собой набор открытых типов и API-интерфейсов в пространствах имен [ Cкачайте файл, чтобы посмотреть ссылку ] и [ Cкачайте файл, чтобы посмотреть ссылку ]. Цель TPL  повышение производительности труда разработчиков за счет упрощения процедуры добавления параллелизма в приложения.TPL динамически масштабирует степень параллелизма для наиболее эффективного использования всех доступных процессоров. Кроме того, в библиотеке параллельных задач осуществляется секционирование работы, планирование потоков в пуле [ Cкачайте файл, чтобы посмотреть ссылку ], поддержка отмены, управление состоянием и выполняются другие низкоуровневые задачи.Используя библиотеку параллельных задач, можно повысить производительность кода, сосредоточившись на работе, для которой предназначена программа.
Начиная с .NET Framework 4 библиотека параллельных задач является предпочтительным способом создания многопоточного и параллельного кода.Однако не всякий код подходит для параллелизации; например, если цикл за каждую итерацию выполняет небольшой объем работ или выполняется для небольшого числа итераций, из-за дополнительной нагрузки, которую параллелизация оказывает на систему, код может выполняться медленнее.Кроме того, параллелизация, как и любой многопоточный код, усложняет выполнение программы.Хотя библиотека параллельных задач упрощает многопоточные сценарии, рекомендуется иметь базовое понимание понятий потоков, например блокировки, взаимоблокировки и состояния гонки, чтобы эффективно использовать библиотеку параллельных задач.

Parallel LINQ (PLINQ)

Параллельный LINQ (PLINQ) является параллельной реализацией LINQ to Objects. PLINQ реализует полный набор стандартных операторов запроса LINQ как методы расширения для пространства имен T:System.Linq и имеет дополнительные операторы для параллельных операций. PLINQ объединяет простоту и удобство чтения синтаксиса LINQ с мощностью параллельного программирования. Подобно коду, предназначенному для библиотеки параллельных задач, запросы PLINQ масштабируют в степень параллелизма на основе возможностей главного компьютера.
Во многих сценариях PLINQ может значительно увеличить скорость запросов LINQ, более эффективно используя все доступные ядра на главном компьютере. Повышенная производительность увеличивает вычислительную мощностью на рабочем столе.

Modern UI for WPF (MUI)

Набор элементов управления и стилей преобразования приложения WPF в современное приложение. Этот проект с открытым исходным кодом является побочным продуктом XAML Spy, визуального исполнения инспектора для Silverlight, Windows Phone, Windows Store и WPF.



Другие скрин-шоты см. [ Cкачайте файл, чтобы посмотреть ссылку ]


Task-Based Async Model
Источник [ Cкачайте файл, чтобы посмотреть ссылку ]
Термин эффективность используется часто, когда речь идет о приложениях, но это на самом деле довольно расплывчатый термин. Есть по крайней мере, два аспекта эффективности: время запуска приложений и собственно производительности (throughput). Оба они могут быть измерены и описаны фактическими числами. Истинный тест приложения, однако, определяется восприятием конечного пользователя. Стоп-часы могут рассказать нам одну вещь, но пользователь может увидеть что-то другое. Восприятие конечного пользователя является основой асинхронности и связанных с ними функций, которые мы построили в .NET Framework 4.5. По существу, мы можем обеспечить более гибкий пользовательский интерфейс, если некоторые из более дорогих операций приложения могут быть отложены. Это позволит порадовать конечного пользователя и это поможет нам более эффективно использовать вычислительные ресурсы, чем было бы возможно ранее. В то время как вы всегда были в состоянии реализовать эту модель в .NET Framework, на практике реализация была проблематичной. Это было исправлено в .NET Framework 4.5 Beta.
Продолжение см. [ Cкачайте файл, чтобы посмотреть ссылку ]

Процесс разработки приложений на платформе CLR
Отдельные компоненты программы, разрабатываемой для платформы CLR в среде MVS, можно разрабатывать на любом из языков, компилятор которого способен «выдать» CIL-код (Common Intermediate Language – промежуточный язык). MVS-2010 поддерживает языки С++, C++/CLI, C#, VB.NET, F#. (CLI – Common Languge Infrastructure).



Рис. 1.2 Последовательность создания и выполнения программы на платформе CLR

Основные достоинства CLR и FCL
Вот далеко не полный список преимуществ CLR и FCL:
Единая программная модель. В отличие от существующего подхода, когда одни функции ОС доступны через процедуры динамически подключаемых библиотек (DLL), а другие через СОМ-объекты, весь прикладной сервис представлен общей объектно-ориентированной программной моделью.
Упрощенная модель программирования. CLR избавляет от работы с разными потаенными структурами, как это было с Win32 и СОМ. Так, разработчику не нужно разбираться с реестром, глобальными уникальными идентификаторами (GUID), IUnknown, AddRef, Release, HRESULT и т. д. CLR не просто позволяет разработчику абстрагироваться от этих концепций их просто нет в CLR в каком-бы то ни было виде. Конечно, если вы хотите написать приложение .NET Framework, которое взаимодействует с существующим не-.NET кодом, вам нужно разбираться во всех этих концепциях.
Отсутствие проблем с версиями. Все Windows-разработчики знают о проблемах совместимости версий, известных под названием «ад DLL». Этот «ад» возникает, когда компоненты, устанавливаемые для нового приложения, заменяют компоненты старого приложения, и в итоге последнее начинает вести себя странно или перестает работать. Архитектура .NET Framework позволяет изолировать прикладные компоненты, так что приложение всегда загружает компоненты, с которыми оно строилось и тестировалось. Если приложение работает после начальной установки, оно будет работать всегда.
Упрощенное развертывание и деинсталляция. Сегодня Windows-приложения очень трудно устанавливать и разворачивать: обычно нужно создать массу файлов, параметров реестра и ярлыков. К тому же полностью удалить приложение практически невозможно. В Windows 2000 Microsoft представила новый механизм установки, решающий многие проблемы, но по-прежнему остается вероятность, что его потребители не все сделают правильно. С приходом .NET Framework все эти проблемы остаются в прошлом. Компоненты .NET Framework не связаны с реестром. По сути, установка приложений .NET Framework сводится лишь к копированию файлов в нужные каталоги и созданию ярлыков в меню Start (Пуск), на рабочем столе или на панели быстрого запуска задач. Удаление же приложений сводится к удалению файлов.
Работа на многих платформах. При компиляции кода для .NET Framework компилятор генерирует код на общем промежуточном языке (common intermediate language, CIL), а не традиционный код, состоящий из процессорных команд. При исполнении CLR транслирует CIL в команды процессора. Поскольку трансляция выполняется в период выполнения, генерируются команды конкретного процессора. Это значит, что вы можете развертывать свое приложение .NET Framework на любой машине, где работает версия CLR и FCL, соответствующая стандарту ЕСМА: с архитектурой х8б, хб4, IA64 и т. д. Пользователи оценят такую возможность при переходе с одной аппаратной платформы или ОС к другой.
Интеграция языков программирования. СОМ поддерживает взаимодействие разных языков .NET Framework обеспечивает интеграцию разных языков, то есть один язык может использовать типы, созданные на других языках. Например, CLR позволяет создать на C++ класс, производный от класса, реализованного на Visual Basic. В CLR это возможно из-за наличия общей системы типов (Common Type System, CTS), которую должны использовать все языки, ориентированные на CLR. Общеязыковая спецификация (Common Language Specification, CLS) определяет правила, которым должны следовать разработчики компиляторов, чтобы их языки интегрировались с другими. Сама Microsoft предлагает несколько таких языков: C++/CLI (C++ с управляемыми расширениями), С#, Visual Basic .NET и F#. Кроме того, другие компании и учебные заведения создают компиляторы других языков, совместимых с CLR.
Упрощенное повторное использование кода. Все описанные выше механизмы позволяют создавать собственные классы, предоставляющие сервис сторонним приложениям. Теперь многократное использование кода становится исключительно простым и создается большой рынок готовых компонентов (типов).
Автоматическое управление памятью (сбор мусора). Программирование требует большого мастерства и дисциплины, особенно когда речь идет об управлении использованием ресурсов (файлов, памяти, пространства экрана, сетевых соединений, ресурсов баз данных и прочих). Одна из самых распространенных ошибок небрежное отношение к освобождению этих ресурсов, что может привести к некорректному выполнению программы в непредсказуемый момент. CLR автоматически отслеживает использование ресурсов, гарантируя, что не произойдет их утечки. По сути, она исключает возможность явного «освобождения» памяти.
Проверка безопасности типов. CLR может проверять безопасность использования типов в коде, что гарантирует корректное обращение к существующим типам. Если входной параметр метода объявлен как 4-байтное значение, CLR обнаружит и предотвратит передачу 8-байтного значения в качестве значения этого параметра. Безопасность типов также означает, что управление может передаваться только в определенные точки (точки входа методов). Невозможно указать произвольный адрес и заставить программу исполняться, начиная с этого адреса. Совокупность всех этих защитных мер избавляет от многих распространенных программных ошибок (например, от возможности использования переполнения буфера для «взлома» программы).
Развитая поддержка отладки. Поскольку CLR используется для многих языков, можно написать отдельный фрагмент программы на языке, наиболее подходящем для конкретной задачи, CLR полностью поддерживает отладку многоязыковых приложений.
Единый принцип обработки сбоев. Один из самых неприятных моментов Windows-программирования несогласованный стиль сообщений о сбоях. Одни функции возвращают коды состояний Win32, другие HRESULT, третьи генерируют исключения. В CLR обо всех сбоях сообщается через исключения, которые позволяют отделить код, необходимый для восстановления после сбоя, от основного алгоритма. Такое разделение облегчает написание, чтение и сопровождение программ. Кроме того, исключения работают в многомодульных и многоязыковых приложениях. И в отличие от кодов состояний и HRESULT исключения нельзя проигнорировать. CLR также предоставляет встроенные средства анализа стека, заметно упрощающие поиск фрагментов, вызывающих сбои.
Безопасность. Традиционные системы безопасности обеспечивают управление доступом на основе учетных записей пользователей. Это проверенная модель, но она подразумевает, что любому коду можно доверять в одинаковой степени. Такое допущение оправданно, когда весь код устанавливается с физических носителей (например, с компакт-диска) или с доверенных корпоративных серверов. Но по мере увеличения объема мобильного кода, например Web-сценариев, приложений, загружаемых из Интернета, и вложений, содержащихся в электронной почте, нужен ориентированный на код способ контроля за поведением приложений. Такой подход реализован в модели безопасности доступа к коду.
Взаимодействие с существующим кодом. В Microsoft понимают, что разработчики накопили огромный объем кода и компонентов. Переписывание всего этого кода, так чтобы он задействовал все достоинства .NET Framework, значительно замедлило бы переход к этой платформе. Поэтому в .NET Framework реализована полная поддержка доступа к СОМ-компонентам и Win32-функциям в существующих DLL.

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

Среда разработки: Microsoft Visual Studio
Visual Studio предлагаемая Microsoft среда разработки. Работа над ней шла долгие годы, и в нее включены многие функции, специфические для .NET Framework.. Как и любая другая хорошая среда разработки, Visual Studio включает средства управления проектами, редактор исходного текста, конструкторы пользовательского интерфейса, мастера, компиляторы, компоновщики, инструменты, утилиты, документацию и отладчики. Она позволяет создавать приложения для 32- и 64-разрядных Windows-платформ, а также новой платформы .NET Framework. Одно из важнейших усовершенствований возможность работы с разными языками и приложениями различных типов в единой среде разработки.
Microsoft также предоставляет новый набор инструментов .NET Framework SDK. Он распространяется бесплатно и включает компиляторы всех языков, множество утилит и объемную документацию. С помощью этого SDK вы можете создавать приложения для .NET Framework без Visual Studio. Вам потребуется лишь свой редактор текстов и средство управления проектами. При этом вы не сможете создавать приложения Web Forms и Windows Forms путем буксировки пиктограмм на форму.

Язык F#
F# это мультипарадигмальный язык программирования из семейства языков .NET Framework, поддерживающий функциональное программирование в дополнение к императивному (процедурному) и объектно-ориентированному программированию. Структура F# во многом схожа со структурой OCaml с той лишь разницей, что F# реализован поверх библиотек и среды исполнения .NET. Язык был разработан Доном Саймом (англ. Don Syme) в Microsoft Research в Кембридже, в настоящее время его разработку ведет Microsoft Developer Division. F# достаточно тесно интегрируется со средой разработки Visual Studio и включён в поставку Visual Studio 2010/2012/2013; разработаны также компиляторы для Mac и Linux.

Некоторые задачи решаются значительно проще и яснее с использованием F# (по сравнению с решениями на доминирующих ОО-языках), особенно те, что используют математический стиль программирования. Например, написание компилятора требует работы со структурами данных и преобразований над ними.

Microsoft интегрировала среду разработки F# в Visual Studio 2010. Компания планирует активно внедрять данный язык в разработку программных систем, которые сами с течением времени смогут масштабироваться, например, в зависимости от количества пользователей. Данное достоинство непросто реализовать в императивных языках программирования.

4 ноября 2010 года код компилятора F# и основных библиотек к нему опубликован под Apache License 2.0.
Примеры программ
Все примеры даны для MVS 2008 на языке C#, если это не оговорено отдельно.
Пример консольного приложения
using System;
namespace MyNS
{
class MyProgram // public sealed static internal
{
public virtual void F() { }
static void Main(String[] args) // static - обязательно
{

Console.WriteLine("args[0]={0}", args[0]);

float[] Vec = new float[123];
foreach (int i in Vec) Vec[i] = 0;

switch (args.Length)
{
case 0: Console.WriteLine("Нет параметров"); break;
case 1: Console.WriteLine(args[0]); break;
case 2: Console.WriteLine(args[1]); break;
default: Console.WriteLine("Слишком много параметров!"); break;
}
int k = 123;
Console.WriteLine("k={0}, k={1}", k, k.ToString());
Console.WriteLine("Введи число>");
String s=Console.ReadLine(); // вводим число как строку символов
k = Int32.Parse(s);
Double d = Double.Parse(s);
d = Convert.ToDouble(s);
k = Int32.Parse(Console.ReadLine());
Console.ReadKey();
}
}
}
/*
MyFirstProgram 777 ->
args[0]=MyFirstProgram
777
k=123, k=123
*/
Класс стек на основе связного списка


Шаблонный класс список
В данном подразделе иллюстрируются реализация собственного шаблонного класса односвязный список MyList, наследующего интерфейс IEnumerable (см. рис.3.1). IEnumerable – это базовый интерфейс для всех не обобщенных коллекций, которые могут быть перечислены. IEnumerable содержит один метод GetEnumerator(), который возвращает IEnumerator. IEnumerator предоставляет возможность выполнить перебор элементов коллекции с использованием свойства Current, методов MoveNext() и Reset(). Проект List.
Полученный шаблонный класс MyList используется как родительский для класса SortedList, который реализует метод BubbleSort(), назначение и алгоритм которого раскрыт в его наименовании.
В качестве единственного параметра шаблонного класса выступает простой класс Person, имеющий всего два атрибута: имя и возраст некоторого индивида.


Рис. 3.1. Диаграммы классов приложения



















Класс Environment: получение сведений о компьютере
/ http://www.bogotobogo.com/CSharp/csharp_system_members.php
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GetCompProp
{
class Program
{

·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
· Чтение текстового файла

В данной программе иллюстрируется чтение данных разных типов из текстового файла. Содержимое файла может быть таким:
Прогульщиков 12,5 1992
Ленивый 23,5 1993
Красивая 9060,90 1994

Разделителем полей записи выбран пробел, но пробел в конце записи файла не допускается. Текстовый файл с символами кириллицы надо сохранить в кодировке Unicode:
File->Advanced Save Options->Encoding->Unicode (UTF-8 ...) - CodePage 65001
Файл проще всего разместить в каталоге debug проекта или использовать стандартный диалог по получению имени файла.










Стандартный диалог по выбору имени файла можно организовать так:
using System;
using System.IO; /* необходимо для использования Directory.GetCurrentDirectory()*/
//using System.Collections.Generic;
//using System.Linq;
//using System.Text;
//using System.ComponentModel;
//using System.Data;
//using System.Drawing;
using System.Windows.Forms; /* необходимо для использования OpenFileDialog */

class Program
{
[STAThreadAttribute]// необходимо для использования OpenFileDialog
static void Main(string[] args)
{
OpenFileDialog openFileDialog1 = new OpenFileDialog();
openFileDialog1.DefaultExt = "txt";
openFileDialog1.Filter = "Text files (*.txt)|*.txt";
openFileDialog1.InitialDirectory =
Directory.GetCurrentDirectory();
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
/* читаем весь файл и выводим его содержимое в окно*
StreamReader sr = new
StreamReader(openFileDialog1.FileName);
MessageBox.Show(sr.ReadToEnd());
sr.Close();
}
}
}
Основы типов
Все типы производные от System.Object
В CLR каждый объект прямо или косвенно является производным от System.Object. Это значит, что следующие определения типов идентичны:

// Тип, неявно производный от Object
// Тип, явно производный от Object

class Employee {
...
}
class Employee : System.Object {
...
}


Благодаря тому, что все типы в конечном счете являются производными от System.Object, любой объект любого типа гарантированно имеет минимальный набор методов. Открытые экземплярные методы класса System.Object перечислены в табл. 3.1.

Табл. 3.1. Открытые методы System.Object
Открытый метод
Описание

Equals
Возвращает true, если два объекта имеют одинаковые значения

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

ToString
По умолчанию возвращает полное имя типа {this.GetType().FullName). На практике этот метод переопределяют, чтобы он возвращал объект String, содержащий состояние объекта в виде строки. Например, переопределенные методы для таких фундаментальных типов, как Boolean и Int32, возвращают значения объектов в строковом виде. Кроме того, переопределение метода часто применяют при отладке: вызов такого метода позволяет получить строку, содержащую значения полей объекта. Считается, что ToString «знает» о CultureInfo, связанном с вызывающим потоком

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


Кроме того, типы, производные от System.Object, имеют доступ к защищенным методам (см. табл. 3.2).
Табл. 3.2. Защищенные методы System.Object
Защищенный метод
Описание

MemberwiseClone
Этот невиртуальный метод создает новый экземпляр типа и присваивает полям нового объекта соответствующие значения объекта this. Возвращается ссылка на созданный экземпляр

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


Пример с константой:
Console.WriteLine("123.ToString()={0}", 123.ToString()); // 123
Console.WriteLine("123.GetType().FullName={0}",
123.GetType().FullName); // 123.GetType().FullName=System.Int32

CLR требует, чтобы все объекты создавались с помощью оператора new (который порождает IL-команду newobj). Объект Employee создается так:
Employee e = new Employee("ConstructorParam1");

Оператор new делает следующее.
Вычисляет число байт, необходимых всем экземплярным полям типа и всем его базовым типам вплоть до и включая System.Object (в котором отсутствуют собственные экземплярные поля). Каждый объект кучи требует дополнительных членов, они называются указатель на объект-тип (type object pointer) и индекс блока синхронизации {SyncBlockIndex) и используются CLR для управления объектом. Байты этих дополнительных членов добавляются к байтам, необходимым для размещения самого объекта.
Выделяет память для объекта, резервируя необходимое для данного типа число байт в управляемой куче байтов.
Инициализирует указатель на объект-тип и SyncBlockIndex.
Вызывает конструктор экземпляра типа с параметрами, указанными при вызове new (в предыдущем примере это строка «ConstructorParaml»). Хотя многие компиляторы помещают в конструктор вызов конструктора базового типа. Большинство компиляторов автоматически создает в конструкторе код вызова конструктора базового класса. Каждый конструктор выполняет инициализацию определенных в соответствующем типе полей. В частности, вызывается конструктор System.Object, но он ничего не делает и просто возвращает управление. Это легко проверить, загрузив в ILDasm.exe библиотеку MSCorLib.dll и изучив метод-конструктор типа System.Object.

Выполнив все эти операции, new возвращает ссылку (или указатель) на вновь созданный объект. В предыдущем примере кода эта ссылка сохраняется в переменной е типа Employee.
Кстати, у оператора new нет пары оператора delete, то есть нет явного способа освобождения памяти, занятой объектом. Сборкой мусора занимается CLR, которая автоматически находит объекты, ставшие ненужными или недоступными, и освобождает занимаемую ими память.
Приведение типов
Одна из важнейших особенностей CLR безопасность типов (type safety). В период выполнения тип объекта всегда известен CLR. Точно определить тип объекта позволяет GetType. Поскольку это невиртуальный метод, никакой тип не сможет сообщить о себе ложные сведения. Так, тип Employee не может переопределить метод GetType, чтобы тот вернул тип SpaceShuttle.
При разработке программ часто прибегают к приведению объекта к другим типам. CLR разрешает привести тип объекта к его собственному типу или любому из его базовых типов. Каждый язык программирования по-своему осуществляет приведение типов. Например, в С# нет специального синтаксиса для приведения типа объекта к его базовому типу, поскольку такое приведение считается безопасным неявным преобразованием. Однако для приведения типа к производному от него типу разработчик на С# должен ввести операцию явного приведения типов неявное преобразование приведет к ошибке. Вот пример приведения к базовому и производному типам:
// Этот тип неявно наследует типу System.Object
internal class Employee
{
...
}

public sealed class Program
{
public static void Main()
{
/* Приведение типа не требуется, так как new возвращает объект
Employee, a Object - это базовый тип для Employee*/
Object оbj = new Employee();
/* Приведение типа обязательно, так как Employee - производный от Object. В других языках (таких как Visual Basic) компилятор не потребует явного приведения*/
Employee emp = (Employee) оbj;
}
}

Этот пример демонстрирует, что необходимо компилятору для компиляции кода. А что произойдет в период выполнения? CLR проверит операции приведения, чтобы преобразования типов осуществлялись либо к фактическому типу объекта, либо к одному из его базовых типов. Вот код, который успешно компилируется, но в период выполнения вызывает исключение InvalidCastException:
internal class Employee
{
...
}
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 инструктирует компилятор добавлять к каждому имени типа определенную приставку это избавляет программиста от необходимости писать массу лишнего кода.
Как связаны пространства имен и сборки
Пространство имен и сборка (файл, содержащий реализацию типа) могут быть не связаны. В частности, различные типы, принадлежащие одному пространству имен, могут быть реализованы в нескольких сборках.
Например, тип System.IO.FileStream реализован в сборке MSCorLib.dll, a System.IO.FileSystemWatcher в System.dll. В действительности сборка System.dll даже не поставляется в составе .NET Framework.
В одной сборке могут содержаться типы из разных пространств имен. Так, в сборке MSCorLib.dll находятся типы System.Int32 и System.Text.StringBuilder.
В документации .NET Framework SDK четко показано, к каким пространствам имен принадлежат те или иные типы и в каких сборках находятся реализации типов. На рис. 3.1 справа от раздела Syntax показано, что тип ResXFileRef относится к пространству имен SystemResources, однако его реализация находится в сборке System.Windows.Forms.dll. Чтобы скомпилировать код, ссылающийся на тип ResXFileRef следует добавить в код директиву using SystemResources; а также использовать параметр /r:System.Windows.Forms.dll компилятора.



Рис. 3.1. Сведения о пространстве имен и сборке для конкретного типа в документации к .NET Framework SDK
Элементарные, ссылочные и значимые типы
В этой главе речь идет о разновидностях типов, с которыми вы будете иметь дело при программировании для Microsoft .NET Framework. Важно, чтобы все разработчики четко осознавали разницу в их поведении. Приступая к изучению .NET Framework, я (Рихтер) толком не понимал, в чем разница между элементарными, ссылочными и значимыми типами, и поэтому невольно напустил в свой код трудно вылавливаемых «жучков» и снизил его эффективность. Надеюсь, мой опыт и объяснения разницы между этими типами помогут вам избавиться от лишней головной боли и повысить производительность работы.
Элементарные типы в языках программирования
Некоторые типы данных применяют так широко, что для работы с ними во многих компиляторах предусмотрен упрощенный синтаксис. Например, целую переменную можно создать так:
System.Int32 а = new System.Int32();

Конечно, подобный синтаксис для объявления и инициализации целой переменной кажется громоздким. К счастью, многие компиляторы (включая С#) позволяют использовать и более простые выражения, например:
int a = 0;

Такой код читается намного лучше, да и компилятор в обоих случаях генерирует идентичный IL-код для System.Int32. Типы данных, которые поддерживаются компилятором напрямую, называются элементарными типами (primitive types) и отображаются им в типы из библиотеки классов .NET Framework Class Library (FCL). Так, С#-типу int соответствует System.Int32. Значит, весь следующий код компилируется без ошибок и преобразуется в одинаковые команды IL (в приведенном коде регистр символов важен):
int а = 0; // Самый удобный синтаксис
System.Int32 a = 0; // Удобный синтаксис
int a = new int(); // Неудобный синтаксис
System.Int32 a = new System.Int32(); // Самый неудобный синтаксис

В табл. 4.1 представлены типы FCL и соответствующие им элементарные типы С#. В других языках типам, удовлетворяющим общеязыковой спецификации Common Language Specification (CLS), будут соответствовать аналогичные элементарные типы. Однако поддержка языком типов, не удовлетворяющих требованиям CLS, необязательна.

Табл. 4.1. Элементарные типы С# и соответствующие типы FCL
Элементарный тип С#
Тип FCL
Совместимость c CLS
Описание

sbyte
System.SByte
Нет
8-разрядное значение со знаком

byte
System.Byte
Да
8-разрядное значение без знака

short
System.Intl6
Да
16-разрядное значение со знаком

ushort
System.UIntl6
Нет
16-разрядное значение без знака

int
System.Int32
Да
32-разрядное значение со знаком

uint
System.UInt32
Нет
32-разрядное значение без знака

long
System.Int64
Да
64-разрядное значение со знаком

ulong
System.UInt64
Нет
64-разрядное значение без знака

char
System.Char
Да
16-разрядный символ Unicode (char никогда не представляет 8-разрядное значение, как в неуправляемом коде на C++)

float
System.Single
Да
32-разрядное float в стандарте IEЕЕ

double
System.Double
Да
64-разрядное float в стандарте IEEE

bool
System.Boolean
Да
Булево значение (True или False)

decimal
System.Decimal
Да
128-разрядное значение с плавающей точкой повышенной точности, часто используемое для финансовых расчетов, где недопустимы ошибки округления. Один разряд числа это знак, в следующих 96 разрядах помещается само значение, следующие 8 разрядов степень числа 10, на которое делится 96-разрядное число (может быть в диапазоне от 0 до 28). Остальные разряды не используются

object
System.Object
Да
Базовый тип для всех типов

string
System.String
Да
Массив символов


Иначе говоря, можно полагать, что компилятор С# автоматически предполагает, что во всех файлах исходного кода есть следующие директивы using:
using sbyte = System.SByte;
using byte = System.Byte;
using short = System.Int16;
using ushort = System.UInt16;
using int = System.Int32;
using uint = System.UInt32;

Я не могу согласиться со следующим утверждением из спецификации языка С#: «С точки зрения стиля программирования предпочтительней использовать ключевое слово, а не полное системное имя типа». Я стараюсь использовать имена типов FCL и избегать имен элементарных типов. На самом деле мне бы хотелось, чтобы имен элементарных типов не было совсем, а разработчики употребляли только имена FCL-типов. И вот почему.
Мне попадались разработчики, не понимавшие, что использовать в коде: string или String. В С# это не важно, так как ключевое слово string в точности преобразуется в FCL-тип System.String.
В С# long отображается в System.Int64, но в другом языке это может быть Int16 или Int32. Как известно, в C++ с управляемыми расширениями long трактуется как Int32. Если кто-то возьмется читать код, написанный на новом для себя языке, то назначение кода может быть неверно им истолковано. Многим языкам незнакомо ключевое слово long, и их компиляторы не пропустят код, где оно встречается.
У многих FCL-типов есть методы, в имена которых включены имена типов. Например, у типа BinaryReader есть методы ReadBoolean, ReadInt32 и ReadSingle и т. д., а у типа System.Convert методы ToBoolean, Tolnt32 и ToSingle и т. д. Вот вполне приемлемый код, в котором строка, содержащая float, кажется мне неестественной, и сразу не очевидно, что код корректный:
BinaryReader br = new BinaryReader(...);
float val = br.ReadSingle(); /* Код правильный, но выглядит
неестественно*/
Single val = br.ReadSingle(); /* Код правильный и выглядит
нормально*/

По этим причинам я буду использовать в этой книге только имена FCL-типов.
Скорее всего, следующий код во многих языках благополучно скомпилируется и выполнится:
Int32 i=5;// 32-разрядное число
Int64 I = i; // Неявное приведение типа к 64-разрядному значению

Но, если вспомнить, что говорилось о приведении типов ранее, можно решить, что он компилироваться не будет. Все-таки System.Int32 и System.Int64 разные типы и не приводятся друг к другу. Могу вас обнадежить: код успешно компилируется и делает все, что ему положено. Объясню, почему.
Дело в том, что компилятор С# неплохо разбирается в элементарных типах и применяет свои правила при компиляции кода. Иначе говоря, он распознает наиболее распространенные шаблоны программирования и генерирует такие IL-кoманды, благодаря которым исходный код работает, как требуется. В первую очередь это относится к приведению типов, литералам и операторам, примеры которых мы рассмотрим ниже.
Начнем с того, что компилятор выполняет явное и неявное приведение между элементарными типами, например:
Int32 i = 5; // Неявное приведение Int32 к Int32
Int64 I = i; // Неявное приведение Int32 к Int64
Single s = i; // Неявное приведение Int32 к Single
Byte b = (Byte) i; // Явное приведение Int32 к Byte
Int16 v = (Int16) s; // Явное приведение Single к Int16

С# разрешает неявное приведение типа, если это преобразование «безопасно», то есть не сопряжено с потерей данных; пример преобразование из Int32 в Intб4. Однако для преобразования с риском потери данных С# требует явного приведения типа. Для числовых типов «небезопасное» преобразование означает «связанное с потерей точности или величины числа». Так, преобразование из Int32 в Byte требует явного приведения к типу, так как при больших величинах Int32 будет потеряна точность; требует приведения и преобразование из Single в Int64, поскольку число Single может оказаться больше, чем допустимо для Int64.
Разные компиляторы могут создавать различный код для выполнения приведения. Например, в случае приведения числа 6.8 типа Single к типу Int32 одни компиляторы создадут код, который поместит в Int32 число 6, а другие округлят результат до 7. Между прочим, в С# дробная часть всегда отбрасывается. Точные правила приведения для элементарных типов вы найдете в разделе спецификаций языка С#, посвященном преобразованиям («Conversions»).
Помимо приведения, компилятор знает и о другой особенности элементарных типов: к ним применима литеральная форма записи. Литералы сами по себе считаются экземплярами типа, поэтому можно вызывать экземплярные методы, например так:
Console.WriteLine(123.ToString() + 456.ToString()); // "123456"

Кроме того, благодаря тому, что выражения, состоящие из литералов, вычисляются на этапе компиляции, возрастает скорость выполнения приложения.
Boolean found = false; // В готовом коде found присваивается 0
Int32 х = 100 + 20 + 3; // В готовом коде х присваивается 123
String s = "а " + "be"; // В готовом коде s присваивается "a be"

И, наконец, компилятор знает, как и в каком порядке интерпретировать встретившиеся в коде операторы (в том числе +, -, *, /, %, &, л, |, ==, !=, >, <, >=, <=, «, », ~, !, ++, -- и т. п.):
Int32 х = 100; // Оператор присваивания
Int32 у = х + 23; // Операторы суммирования и присваивания
Boolean LessThanFifty = (у < 50); // Операторы "меньше чем" и присваивания

Проверяемые и непроверяемые операции для элементарных типов
Программистам должно быть хорошо известно, что многие арифметические операции над элементарными типами могут привести к переполнению:
Byte b = 100;
b = (Byte) (b + 200); // После этого b равно 44

Такое переполнение «втихую» обычно в программировании не приветствуется, и, если его не выявить, приложение будет вести себя непредсказуемо. Изредка, правда (скажем, при вычислении хешей или контрольных сумм), такое переполнение не только приемлемо, но и желательно.
В каждом языке свои способы обработки переполнения. С и C++ не считают переполнение ошибкой и разрешают усекать значения; приложение не прервет свою работу. А вот Visual Basic всегда рассматривает переполнение как ошибку и, обнаружив его, генерирует исключение.
Внимание! Арифметические операции в CLR выполняются только над 32- и 64-разрядными числами. Поэтому b и 200 сначала преобразуются в 32-разрядные (или в 64-разрядные, если хотя бы одному из операндов недостаточно 32 разрядов) значения, а затем уже суммируются. Поэтому 200 и b (их размер меньше 32-разрядов) преобразуются в 32-разрядные значения и суммируются. Полученное 32-разрядное число, прежде чем поместить его обратно в переменную b, нужно привести к типу Byte. Так как в данном случае С# не делает неявного приведения типа, во вторую строку потребовалось ввести приведение к типу Byte.

В CLR есть IL-команды, позволяющие компилятору по-разному реагировать на переполнение. Так, суммирование двух чисел выполняет команда add, не реагирующая на переполнение, и команда add.ovf, которая при переполнении генерирует исключение System.OverflowException. Кроме того, в CLR есть аналогичные IL-команды для вычитания (sub/sub.ovf), умножения (mul/mul.ovf) и преобразования данных (conv/conv.ovf).
Пишущий на С# программист может сам решать, как обрабатывать переполнение: по умолчанию проверка переполнения отключена. Это значит, что компилятор генерирует для операций сложения, вычитания, умножения и преобразования IL-команды без проверки переполнения. В результате код выполняется быстро, но тогда разработчики должны быть уверены в отсутствии переполнения либо его возникновение должно быть специально предусмотрено.
Чтобы включить управление процессом обработки переполнения на этапе компиляции, добавьте в командную строку компилятора параметр /checked+. Он сообщает компилятору, что для выполнения сложения, вычитания, умножения и преобразования должны быть сгенерированы IL-команды с проверкой переполнения. Такой код медленнее, так как CLR тратит время на проверку этих операций, ожидая переполнение. Когда оно возникает, CLR генерирует исключение OverflowException. Код приложения должен предусматривать корректную обработку этого исключения.
Однако программистам вряд ли подойдет включение или отключение режима проверки переполнения во всем коде. У них должна быть возможность самим решать, как реагировать на переполнение в каждом случае. И С# предлагает такой механизм гибкого управления проверкой в виде операторов checked и unchecked. Например (предполагается, что компилятор по умолчанию создает код без проверки):
UInt32 invalid = unchecked((UInt32) -1); // ОК

А вот пример с использованием оператора checked (предполагается, что компилятор по умолчанию создает код без проверки):
Byte b = 100;
b = checked((Byte) (b + 200)); // Генерируется OverflowException

Здесь b и 200 преобразуются в 32-разрядные числа и суммируются; результат равен 300. Затем преобразование 300 в Byte генерирует исключение OverflowException. Если приведение к Byte вывести из оператора checked, исключения не будет:
b = (Byte) checked(b + 200); // b содержит 44; нет OverflowException

Наряду с операторами checked и unchecked, в С# есть одноименные инструкции, позволяющие включить проверяемые или непроверяемые выражения внутрь блока:
checked
{ // Начало проверяемого блока
Byte b = 100;
b = (Byte) (b + 200); // Это выражение проверяется на переполнение
} // Конец проверяемого блока

Кстати, внутри такого блока можно задействовать оператор +=, который немного упростит код:
checked
{ // Начало проверяемого блока
Byte b = 100;
b += 200; // Это выражение проверяется на переполнение
} // Конец проверяемого блока

Внимание! Установка режима контроля переполнения не влияет на работу метода, вызываемого внутри оператора или инструкции checked, так как действие оператора (и инструкции) checked распространяется только на выбор IL-команд сложения, вычитания, умножения и преобразования данных. Пример:
checked
{
// Предположим, SomeMethod пытается поместить 400 в Byte
SomeMethod(400);
/* Возникновение OverflowException в SomeMethod зависит
от наличия в нем операторов проверки*/
}

Используя checked и unchecked, учитывайте следующее.
Включайте в блок checked ту часть кода, в которой возможно переполнение из-за неверных входных данных, например при обработке запросов, содержащих данные, предоставленные конечным пользователем или клиентской машиной.
Включайте в блок unchecked ту часть кода, в которой переполнение не является проблемой, например при вычислении контрольной суммы.
Для кода, где нет операторов и блоков checked и unchecked, делают предположение, что генерация исключения необходима при переполнении, например при вычислении (скажем, простых чисел), когда входные данные известны и переполнение считается ошибкой.

При отладке кода установите параметр компилятора /checked+. Выполнение приложения замедлится, так как система будет контролировать переполнение во всем коде, не помеченном ключевыми словами checked или unchecked. Обнаружив исключение, вы сможете исправить ошибку. При окончательной сборке приложения установите параметр /checked-, что ускорит выполнение приложения, а исключения генерироваться не будут.
Внимание! Тип SystemDecimal стоит особняком. В отличие от многих языков программирования (включая С# и Visual Basic) в CLR Decimal не относится к элементарным типам. В CLR нет IL-команд для работы со значениями Decimal. В документации по .NET Framework сказано, что тип Decimal имеет открытые статические методы-члены Add, Subtract, Multiply, Divide и прочие, а также перегруженные операторы +, -, *, / и т. д.
При компиляции кода с Decimal компилятор создает вызовы членов Decimal, которые и выполняют реальную работу. Поэтому значения Decimal обрабатываются медленнее элементарных типов CLR. Кроме того, раз нет IL-команд для манипуляции числами Decimal, то не будут иметь эффекта ни checked и unchecked, ни соответствующие параметры командной строки компилятора. И любая «небезопасная» операция над Decimal обязательно вызовет исключение OverflowException.
Ссылочные и значимые типы
CLR поддерживает две разновидности типов: ссылочные (reference types) и значимые (value types). Большинство типов в FCL ссылочные, но программисты чаще всего используют значимые. Память для ссылочных типов всегда выделяется из управляемой кучи, а оператор С# new возвращает адрес памяти, где размещается сам объект. При работе с ссылочными типами имейте в виду следующие обстоятельства, относящиеся к производительности приложения:
память для ссылочных типов всегда выделяется из управляемой кучи;
каждый объект, размещаемый в куче, имеет некоторые дополнительные члены, подлежащие инициализации;
незанятые полезной информацией байты объекта обнуляются (это касается полей);
размещение объекта в управляемой куче со временем инициирует сборку мусора.

Если бы все типы были ссылочными, эффективность приложения резко упала бы. Представьте, насколько замедлится выполнение приложения, если при каждом обращении к значению типа Int32 будет выделяться память! Поэтому, чтобы ускорить обработку простых, часто используемых типов, CLR предлагает «облегченные» типы значимые. Экземпляры этих типов обычно размещаются в стеке потока (хотя они могут быть встроены и в объект ссылочного типа). В представляющей экземпляр переменной нет указателя на экземпляр; поля экземпляра размещаются в самой переменной. Поскольку переменная содержит поля экземпляра, то для работы с экземпляром не нужно выполнять разыменовывание (dereference) экземпляра. Благодаря тому, что экземпляры значимых типов не обрабатываются сборщиком мусора, уменьшается интенсивность работы с управляемой кучей и сокращается количество наборов (collections), требуемых приложению на протяжении его существования.
В документации по .NET Framework можно сразу увидеть, какие типы относят к ссылочным, а какие к значимым. Если тип называют классом (class), речь идет о ссылочном типе. Так, классы System.Object, System.Exception, System.IO.FileStream и System.Random это ссылочные типы. В свою очередь значимые типы в документации называют структурами (structure) и перечислениями (enumeration). Например, структуры System.Int32, System.Boolean, System.Decimal, System.TimeSpan и перечисления System.DayOfWeek, System.IO.FileAttributes и SystemDrawing.FontStyle являются значимыми типами.
При внимательном знакомстве с документацией можно заметить, что все структуры являются прямыми потомками абстрактного типа System.ValueType, который в свою очередь является производным от типа System.Object. По умолчанию все значимые типы должны быть производными от System.ValueType. Все перечисления являются производными от типа System.Enum, производного от System.ValueType. CLR и языки программирования по-разному интерпретируют перечисления.
При определении собственного значимого типа нельзя выбрать произвольный базовый тип, однако значимый тип может реализовать один или несколько выбранных вами интерфейсов. Кроме того, в CLR значимый тип является изолированным, то есть не может служить базовым типом для какого-либо другого ссылочного или значимого типа. Поэтому, например, нельзя в описании нового типа указать в качестве базовых Boolean, Char, Int32, Uint64, Single, Double, Decimal и т. д.
Внимание! Многим разработчикам (в частности тем, кто пишет неуправляемый код на C/C++) деление на ссылочные и значимые типы поначалу будет казаться странным. В неуправляемом коде C/C++ вы объявляете тип, и уже код решает, куда поместить экземпляр типа: в стек потока или в кучу приложения. В управляемом коде иначе: разработчик, описывающий тип, указывает, где разместятся экземпляры данного типа, а разработчик, использующий тип в своем коде, управлять этим не может.
Ниже показано различие между ссылочными и значимыми типами:
// Ссылочный тип (поскольку 'class')
class SomeRef { public Int32 x; }
// Значимый тип (поскольку 'struct')
struct SomeVal { public Int32 x; }
static void ValueTypeDemo()
{
SomeRef r1 = new SomeRef(); // Размещается в куче
SomeVal v1 = new SomeVal(); // Размещается в стеке
r1.x = 5; // Разыменовывание указателя
v1.x = 5; // Изменение в стеке
Console.WriteLine(r1.x); // Отображается 5
Console.WriteLine(vi.x); // Также отображается 5

SomeRef г2 = г1; // Копируется только ссылка (указатель)
SomeVal v2 = v1; // Помещаем в стек и копируем члены
г1.х = 8; // Изменяются г1.х и г2.х
v1.x = 9; // Изменяется v1.x, но не v2.x
Console.WriteLine(г1.х); // Отображается 8
Console.WriteLine(r2.x); // Отображается 8
Console.WriteLine(v1.x); // Отображается 9
Console.WriteLine(v2.x); // Отображается 5
}

В этом примере тип SomeVal объявлен с ключевым словом struct, а не более популярным class. В С# типы, объявленные как struct, являются значимыми, а объявленные как class ссылочными. Разницы в поведении ссылочных и значимых типов практически не видно. Поэтому так важно представлять, к какому семейству относится тот или иной тип к ссылочному или значимому: ведь это может существенно повлиять на эффективность кода.



Рис. 4.1 Различие между размещением в памяти значимых и ссылочных типов

В предыдущем примере есть строка:
SomeVal v1 = new SomeVal(); // Размещается в стеке

Может показаться, что экземпляр SomeVal будет помещен в управляемую кучу. Но, поскольку компилятор С# знает, что SomeVal является значимым типом, в сформированном им коде экземпляр SomeVal будет помещен в стек потока. С# также обеспечивает обнуление всех полей экземпляра значимого типа.
Ту же строку можно записать иначе:
SomeVal v1; // Размещается в стеке

Здесь тоже создается IL-код, который помещает экземпляр SomeVal в стек потока и обнуляет все его поля. Единственное отличие в том, что экземпляр, созданный оператором new, C# «считает» инициализированным. Поясню на примере:
/* Две следующие строки компилируются, так как С# считает, что поля
в v1 инициализируются нулем*/
SomeVal v1 = new SomeVal();
Int32 a = v1.x;
// Следующие строки вызовут ошибку компиляции, поскольку С# не считает, что поля в v1 инициализируются нулем*/
SomeVal v1;
Int32 a = v1.x; /* error CS0170: Use of possibly unassigned field 'x'
(ошибка CS0170: Используется поле 'х', которому не присвоено значение)*/

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

Размер экземпляров типа также нужно учитывать, потому что по умолчанию аргументы передаются по значению, при этом поля экземпляров значимого типа копируются, что отрицательно сказывается на производительности. Повторюсь: метод, возвращающий значимый тип, приводит к копированию полей экземпляра в память, выделенную вызывающим кодом в месте возврата из метода, что снижает эффективность работы программы. Поэтому в дополнение к перечисленным условиям следует объявлять тип как значимый, если одно и перечисленных ниже условий верно:
размер экземпляров типа мал (примерно 16 байт или меньше);
размер экземпляров типа велик (более 16 байт) и экземпляры не передаются в качестве параметров метода или не являются возвращаемыми из метода значениями.

Основное достоинство значимых типов в том, что они не размещаются в управляемой куче. Конечно, в сравнении со ссылочными типами, у значимых типов есть и недостатки. Вот некоторые особенности, отличающие значимые и ссылочные типы:
Объекты значимого типа существуют в двух формах: неупакованной (unboxed) и упакованной (boxed). Ссылочные типы бывают только в упакованной форме.
Значимые типы являются производными от System.ValueType. Этот тип имеет те же методы, что и System.Object. Однако System.ValueType переопределяет метод Equals, который возвращает true, если значения полей в обоих объектах совпадают. Кроме того, в System.ValueType переопределен метод GetHashCode, который создает значение хеш-кода с помощью алгоритма, учитывающего значения полей экземпляра объекта. Из-за проблем с производительностью в реализации по умолчанию, определяя собственные значимые типы значений, надо переопределить и написать свою реализацию методов Equals и GetHashCode. О методах Equals и GetHashCode чуть ниже.
Поскольку в объявлении нового значимого или ссылочного типа нельзя указывать значимый тип в качестве базового класса, то создавать в значимом типе новые виртуальные методы нельзя. Методы не могут быть абстрактными и неявно являются изолированными (то есть их нельзя переопределить).
Переменные ссылочного типа содержат адреса объектов в куче. Когда переменная ссылочного типа создается, ей по умолчанию присваивается null, то есть в этот момент она не указывает на действительный объект. Попытка задействовать переменную с таким значением приведет к генерации исключения NullReferenceException. В то же время в переменной значимого типа всегда содержится некое значение соответствующего типа, а при инициализации всем членам этого типа присваивается 0. Поскольку переменная значимого типа не является указателем, при обращении к значимому типу исключение NullReferenceException возникнуть не может. CLR поддерживает понятие особого вида значимого типа, допускающего присваивание null, (nullable types).
Когда переменной значимого типа присваивается другая переменная значимого типа, выполняется копирование всех ее полей. Когда переменной ссылочного типа присваивается переменная ссылочного типа, копируется только ее адрес.
Вследствие сказанного в предыдущем абзаце, несколько переменных ссылочного типа могут ссылаться на один объект в куче, благодаря чему, работая с одной переменной, можно изменить объект, на который ссылается другая переменная. С другой стороны, каждая переменная значимого типа имеет собственную копию данных «объекта», и операции с одной переменной значимого типа не повлияют на другую переменную.
Так как неупакованные значимые типы не размещаются в куче, память, отведенная для них, освобождается сразу при возвращении управления методом, в котором описан экземпляр этого типа. Это значит, что экземпляр значимого типа не получает уведомления (через метод Finalize), когда его память освобождается.

Примечание. Действительно, было бы довольно странно включить в описание значимого типа метод Finalize, так как он вызывается только для упакованных экземпляров. Поэтому многие компиляторы (включая С#, C++/CLI и Visual Basic) не допускают в описании значимых типов методы Finalize. Правда, CLR разрешает включать в описание значимого типа метод Finalize, однако при сборке мусора для упакованных экземпляров значимого типа этот метод не вызывается.
«Как CLR управляет размещением полей для типа», «Упаковка и распаковка значимых типов», «Изменение полей в упакованных размерных типах посредством интерфейсов», «Равенство и тождество объектов», «Хеш-коды объектов» – см. [11].

Основные сведения о членах и типах
В части 2 рассматривались типы и операции, применимые ко всем экземплярам любого типа. Я также пояснил, почему все типы делятся на две категории ссылочные и значимые типы. В этой и последующих главах я покажу, как проектировать типы, используя различные виды членов.
Члены типа
В типе можно определить следующие члены.
Константа идентификатор, определяющий некую постоянную величину. Эти идентификаторы обычно используют для повышения читабельности кода и удобства сопровождения и поддержки. Константы всегда связаны с типом, а не экземпляром типа. В некотором смысле константы всегда статичны.
Поле представляет неизменяемое или изменяемое значение. Поле может быть статическим тогда оно является частью типа. Поле может быть и экземплярным (нестатическим) тогда оно является частью объекта. Я настоятельно рекомендую делать поля закрытыми, чтобы внешний код не мог нарушить состояние типа или объекта.
Конструктор экземпляров метод, служащий для инициализации полей экземпляра при его создании.
Конструктор типа метод, используемый для инициализации статических полей типа.
Метод представляет собой функцию, выполняющую операции, которые изменяют или запрашивают состояние типа (статический метод) или объекта (экземплярный метод). Методы обычно осуществляют чтение и запись полей типов или объектов.
Перегруженный оператор определяет, что нужно проделать с объектом при применении к нему оператора. Перегрузка операторов отсутствует в общеязыковой спецификации CLS, поскольку не все языки программирования ее поддерживают.
Оператор преобразования это метод, определяющий порядок явного или неявного приведения/преобразования объекта из одного типа в другой. Операторы преобразования не входят в спецификацию CLS по той же причине, что и перегруженные операторы.
Свойство представляет собой механизм, позволяющий применить простой (напоминающий обращение к полям) синтаксис для установки или получения части логического состояния типа или объекта, не нарушая это состояние. Свойства чаще всего бывают непараметризованными. Параметризованные свойства обычно используются в классах-наборах.
Событие. Статическое событие это механизм, позволяющий типу посылать уведомление слушающему типу/объекту. Экземплярное (нестатическое) событие является механизмом, позволяющим объекту посылать уведомление слушающему типу/объекту. События обычно инициируются в ответ на изменение состояния типа или объекта, порождающего событие. Событие состоит из двух методов, позволяющих типам или объектам («слушателям») регистрировать и отменять регистрацию-подписку на событие. Помимо этих двух методов, в событиях обычно используется поле-делегат для управления набором зарегистрированных слушателей.
Тип позволяет определять другие вложенные в него типы. Он применяется обычно для разбиения большого, сложного типа на небольшие блоки для упрощения его реализации.

Итак, цель данного раздела не в подробном описании различных членов, а в изложении основных положений и объяснении, что общего между этими членами.
Независимо от используемого языка программирования компилятор должен обработать исходный код и создать метаданные и IL-код для всех членов типа. Формат метаданных един и не зависит от используемого языка программирования. именно поэтому CLR называют общеязыковой исполняющей средой. Метаданные это стандартная информация, которую предоставляют и используют все языки, позволяя коду на одном языке программирования без проблем обращаться к коду на совершенно другом языке.
Стандартный формат метаданных также используется средой CLR для определения порядка поведения констант, полей, конструкторов, методов, свойств и событий во время выполнения. Короче говоря, метаданные это ключ ко всей платформе разработки Microsoft .NET Framework; они обеспечивают интеграцию языков, типов и объектов.
В следующем примере на С# показано определение типа со всеми возможными членами. Этот код успешно компилируется (не без предупреждений), но пользы от него немного всего лишь демонстрация, как компилятор транслирует такой тип и его члены в метаданные. Еще раз напомню, что каждый из членов в отдельности будет детально рассмотрен в следующих главах.
using System;
public sealed class SomeType // 1
{ // Вложенный класс
private class SomeNestedType { } //2
// Константа, неизменяемое и статическое изменяемое поле
private const Int32 SomeConstant =1; // 3
private readonly Int32 SomeReadOnlyField = 2; // 4
private static Int32 SomeReadWriteField = 3; // 5

// Конструктор типа
static SomeType() { } // 6

// Конструкторы экземпляров
public SomeType() { } // 7
public SomeType(Int32 x) { } // 8

// Экземплярный и статический методы
private String InstanceMethod() { return null; } //9
public static void Main() {} //10

// Непараметризованное свойство экземпляра
public Int32 SomeProp //11
{
get { return 0; } //12
set { } //13
}

// Параметризованное свойство экземпляра
public Int32 this[String s] //14
{
get { return 0; } //15
set { } //16
}

// Экземплярное событие
public event EventHandler SomeEvent; // 17
}

После компиляции типа можно просмотреть метаданные с помощью ILDasm.exe (рис. 5.1), например, командой ILDasm.exe Test.exe.



Puc. 5.1. Выходные данные ILDasm.exe с метаданными, созданными на основе вышеприведенного примера

Заметьте, что компилятор генерирует метаданные для всех членов типа. На самом деле для некоторых членов (например, событие, член 17) компилятор создает дополнительные члены (поле и два метода) и метаданные. Сейчас не требуется полностью понимать, что изображено на рисунке, но по мере чтения следующих глав я рекомендую возвращаться к этому примеру и смотреть, как задается тот или иной член и как это влияет на метаданные, генерируемые компилятором.
Пример использования некоторых компонентов класса SomeType:
public sealed class TestForSomeType
{
void TestFunctionForSomeType()
{
int i=SomeType.SomeConstant; /* можно: константа принадлежит
типу */
//i = SomeType.GetConstant(); /* Нельзя обращаться к
экземплярной (нестатической) функции */
SomeType st = new SomeType();
// i=st.SomeConstant; /* нельзя: константа принадлежит типу,
но не объекту*/
i=st.GetConstant(); // можно
String s="SomePar";
i = st[s]; // чтение параметризованного свойства
}
}
Видимость типа
При определении типа с видимостью в рамках файла, а не другого типа, его можно сделать открытым {public) или внутренним (internal). Открытый тип доступен любому коду любой сборки. Внутренний тип доступен только из сборки, где он определен. По умолчанию компилятор С# делает тип внутренним. Вот несколько примеров:
using System;
// Это открытый тип; он доступен из любой сборки
public class ThisIsAPublicType { ... }
// Это внутренний тип; он доступен только из собственной сборки
internal class ThisIsAnlnternalType { ... }
// Это внутренний тип, так как модификатор доступа не указан явно
class ThisIsAlsoAnlnternalType { ... }

Дружественные сборки – см. [11].

Доступ к членам
При определении члена типа (в том числе вложенного) можно указать модификатор доступа к члену. Модификаторы определяют, на какие члены можно ссылаться из кода. В CLR определен свой набор возможных модификаторов доступа, но в каждом языке программирования существует свой синтаксис и термины.
Например, термин Assembly в CLR указывает, что член доступен изнутри сборки, тогда как в С# для этого используется internal.
В табл. 5.1 приведены шесть модификаторов доступа, определяющие уровень ограничения от максимального {Private) до минимального {Public).

Табл. 5.1. Модификаторы доступа к члену
Термин CLR
Термин С#
Описание

Private (Закрытый)
private
Доступен только методам в определяющем типе и вложенных в него типах

Family (Родовой)
protected
Доступен только методам в определяющем типе (и вложенных в него типах) или одном из его производных типов независимо от сборки

Family and Assembly (Родовой и Сборочный)
(не поддерживается)
Доступен только методам в определяющем типе (и вложенных в него типах) и производных типах в определяющей сборке

Assembly (Сборочный)
internal
Доступен только методам в определяющей сборке

Family or Assembly (Родовой или Сборочный)
protected internal
Доступен только методам вложенного типа, производного типа (независимо от сборки) и любым методам определяющей сборки

Public (Открытый)
public
Доступен всем методам во всех сборках


Разумеется, доступ к члену можно получить, только если он определен в видимом типе. Например, если в сборке А определен внутренний тип, имеющий открытый метод, то код сборки Б не сможет вызвать открытый метод, поскольку внутренний тип сборки А не доступен из Б.
В процессе компиляции кода компилятор языка проверяет корректность обращения кода к типам и членам. Обнаружив некорректную ссылку на какие-либо типы или члены, компилятор информирует об ошибке. Помимо этого, во время выполнения JIT-компилятор тоже проверяет корректность обращения к полям и методам при компиляции IL-кода в процессорные команды. Например, обнаружив код, неправильно пытающийся обратиться к закрытому полю или методу, JIT-компилятор генерирует исключение FieldAccessException или MethodAccessException соответственно.
Верификация IL-кода гарантирует правильность обработки модификаторов доступа к членам в период выполнения, даже если компилятор языка проигнорировал проверку доступа. Другая, более вероятная возможность заключается в компиляции кода, обращающегося к открытому члену другого типа (другой сборки); если в период выполнения загрузится другая версия сборки, где модификатор доступа открытого члена заменен защищенным (protected) или закрытым, верификация обеспечит корректное управление доступом.
Если не указать явно модификатор доступа, компилятор С# обычно (но не всегда) выберет по умолчанию закрытый наиболее строгий из всех. CLR требует, чтобы все члены интерфейсного типа были открытыми. Поэтому компилятор С# запрещает программисту явно указывать модификаторы доступа к членам интерфейса, просто делая все члены открытыми.
Примечание. Подробнее о правилах применения в С# модификаторов доступа к типам и членам, а также о том, какие модификаторы С# выбирает по умолчанию в зависимости от контекста объявления, см. в разделе «Declared Accessibility» спецификации языка С#.

Более того, как видно из таблицы, в CLR есть модификатор доступа родовой и сборочный. Но разработчики С# сочли этот атрибут лишним и не включили в язык С#.
Если в производном типе переопределяется член базового типа, компилятор С# требует, чтобы у членов базового и производного типа был одинаковый модификатор доступа. То есть, если член базового класса является защищенным, то и член производного класса также должен быть защищенным. Однако это ограничение языка С#, а не CLR. При наследовании базовому классу CLR позволяет понижать, но не повышать уровень доступа к члену. Например, защищенный метод базового класса можно переопределить в производном классе как открытый, но только не как закрытый. Это необходимо, чтобы пользователь производного типа всегда мог получить доступ к методу базового класса путем приведения к базовому типу. Если бы CLR разрешала накладывать более жесткие ограничения на доступ к методу в производном типе, она выдавала бы желаемое за действительное. (Не совсем понятный абзац – надо проверять и уточнять (ОВН)).
Статические классы
Существуют классы, не предназначенные для создания экземпляров, например Console, Math, Environment и ThreadPool. У этих классов есть только статические методы. В сущности, такие классы существуют лишь для группировки логически связанных членов. Например, класс Math объединяет методы, выполняющие математические операции. В С# такие классы определяются с ключевым словом static. Его разрешается применять только к классам, но не структурам (значимым типам), поскольку CLR всегда разрешает создавать экземпляры значимых типов, и нет способа обойти это ограничение.
Компилятор налагает на статический класс ряд ограничений.
Класс должен быть прямым потомком System.Object наследование любому другому базовому классу лишено смысла, поскольку наследование применимо только к объектам, а создать экземпляр статического класса невозможно.
Класс не должен реализовывать никаких интерфейсов, поскольку методы интерфейса можно вызывать только через экземпляры класса.
В классе можно определять только статические члены (поля, методы, свойства и события). Любые экземплярные члены вызовут ошибку компиляции.
Класс нельзя использовать в качестве поля, параметра метода или локальной переменной, поскольку это подразумевает существование переменной, ссылающейся на экземпляр, что запрещено. Обнаружив подобное обращение со статическим классом, компилятор вернет сообщение об ошибке.

Вот пример статического класса, в котором определены статические члены; сам по себе класс не представляет практического интереса.
using System;
public static clas
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
На рис. 5.2 приведен результат дизассемблирования в ILDasm.exe библиотечной сборки, полученной при компиляции приведенного выше кода. Как видите, определение класса с ключевым словом static заставляет компилятор С# сделать этот класс абстрактным (abstract) и изолированным {sealed). Более того, компилятор не создает в классе метод конструктора экземпляра {.ctor).



Рис. 5.2. Судя по метаданным, отображаемым в ILDasm.exe, класс является абстрактным и изолированным
Частичные классы, структуры и интерфейсы
· "$&
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·у
·
·
·
·H
·H
·
·
·
·
·
·
·
·H
·H
·
·
·
·
·
·
·
·
·H
·H
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·°
·°
·
·
·
·
·
·
·
·
·
·
·
·°
·°
·№
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·°
·°
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·°
·°
·№
·
·
·
·
·
·
·
·H
·H
·
·
·
·
·
·
·
·
·
·
·H
·H
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·ъ
Частичные классы, структуры и интерфейсы поддерживаются исключительно компиляторами С# и некоторых других языков, но CLR ничего о них не знает. На самом деле этот раздел добавлен скорее для полноты изложения материала, ведь книга рассказывает о возможностях CLR при использовании С#.
Ключевое слово partial говорит компилятору С#, что исходный код класса, структуры или интерфейса может располагаться в нескольких файлах. Существуют две основные причины, по которым исходный код разбивается на несколько файлов.
Управление версиями. Представьте, что определение типа содержит большое количество исходного кода. Если этот тип будут одновременно редактировать два программиста, по завершении работы им придется каким-то образом объединять свои результаты, что весьма неудобно. Ключевое слово partial позволяет разбить исходный код типа на несколько файлов, чтобы один и тот же тип могли одновременно редактировать несколько программистов.
Разделители кода. При создании в Microsoft Visual Studio нового проекта Windows Forms или Web Forms, некоторые файлы с исходным кодом создаются автоматически. Они называются шаблонными. При использовании конструкторов форм Visual Studio в процессе создания и редактирования элементов управления формы Visual Studio автоматически генерирует весь необходимый код и помещает его в отдельные файлы. Это значительно повышает продуктивность работы. Раньше автоматически генерируемый код помещался в тот же файл, где программист писал свой исходный код. Проблема была в том, что при случайном изменении сгенерированного кода конструктор форм переставал корректно работать. Начиная с Visual Studio 2005, при создании нового проекта Visual Studio создает два исходных файла: один предназначен для программиста, а в другой помещается код, создаваемый редактором форм. Теперь вероятность случайного изменения генерируемого кода существенно меньше.

Ключевое слово partial применяется к типам во всех файлах с определением типа. При компиляции компилятор объединяет эти файлы, и готовый тип помещается в результирующий файл сборки с расширением .ехе или .dll (или файл модуля .netmodule). Как уже говорилось, частичные типы реализуются только компилятором С#; поэтому все файлы с исходным кодом типа необходимо писать на одном языке и компилировать их в единый модуль.
Компоненты, полиморфизм и версии
ООП на протяжении многих лет пользуется неизменно высокой популярностью. В 70-80 годы объектно-ориентированные приложения были намного меньшего размера и весь код приложения разрабатывался в одной компании. Разумеется, в то время уже были операционные системы и приложения по максимуму использовали их возможности, но современные ОС предлагают намного больше функций.
Сложность программного обеспечения существенно возросла, и пользователи требуют от приложений богатых функциональных возможностей графический интерфейс, меню, поддержку различных устройств ввода-вывода (мышь, принтер, планшет), сетевые функции и т.п. Все это привело к существенному расширению функциональности операционных систем и платформ разработки в последние годы. Более того, сейчас уже не представляется возможным или эффективным писать приложение с нуля и разрабатывать все необходимые компоненты самостоятельно. Современные приложения состоят из компонентов, разработанных многими компаниями. Эти компоненты объединяются в единое приложение в рамках парадигмы ООП.
В компонентной разработке приложений (Component Software Programming) идеи ООП используются на уровне компонентов. Вот некоторые свойства компонента.
Компонент (сборка в .NET) можно публиковать.
Компоненты уникальны и идентифицируются по имени, версии, региональным стандартам и открытому ключу.
Компонент сохраняет свою уникальность (код одной сборки никогда статически не связывается с другой сборкой в .NET применяется только динамическое связывание).
В компоненте всегда четко указана зависимость от других компонентов (ссылочные таблицы метаданных).
В компоненте задокументированы его классы и члены. В С# для этого разрешается помещать в код компонента XML-документацию для этого служит параметр /doc командной строки компилятора.
В компоненте определены требуемые разрешения на доступ. Для этого в CLR существует механизм защиты доступа к коду (Code Access Security).
Опубликованный компонентом интерфейс (объектная модель) не изменяется во всех его служебных версиях. Служебной версией (servicing) называют новую версию компонента, обратно совместимую с оригинальной. Обычно служебная версия содержит исправления ошибок, исправления системы безопасности и небольшие корректировки функциональности. Однако в нее нельзя добавлять новые зависимости или разрешения безопасности.

Как видите, в компонентном программировании большое внимание уделяют управлению версиями. Компоненты постоянно изменяют и поставляют в разное время. Управление версиями существенно повышает сложность компонентного программирования по сравнению с ООП, где все приложение пишет, тестирует и поставляет одна компания.
В .NET номер версии состоит из четырех частей: старший (major) и младший (minor) номера версии, номер компоновки (build) и номер редакции (revision). Например, у сборки с номером 1.2.3.4 старший номер версии 1, младший номер 2, компоновка 3 и редакция 4. Старший и младший номера обычно определяют уникальность сборки, а компоновка и редакция указывают на служебную версию.
Допустим, компания поставила сборку версии 2.7.0.0. Если впоследствии нужно предоставить сборку с исправленными ошибками, выпускают новую сборку, в которой изменяют только номера компоновки и редакции, например 2.7.1.34. То есть сборка является служебной версией и обратно совместима с оригинальной 2.7.0.0).
С другой стороны, если компания выпустит новую версию сборки, в которую внесены значительные изменения и обратная совместимость не гарантируется, нужно изменить старший и/или младший номер версии (например, 3.0.0.0).
Примечание Все сказанное является лишь рекомендацией. К сожалению, CLR никак не анализирует номер версии, и, если сборка зависит от версии 1.2.3.4 другой сборки, CLR будет пытаться загрузить только версию 1.2.3.4 (если только не задействовать перенаправление связывания). Однако Microsoft планирует в будущем изменить загрузчик CLR, чтобы он мог загружать последнюю версию сборки. Например, если загрузчику потребуется версия 1.2.3.4, то, обнаружив версию 1.2.5.0, он загрузит именно ее. Что ж, с нетерпением будем ждать появления такой возможности.

После ознакомления с порядком присвоения номера версии новому компоненту самое время узнать о возможностях CLR и языков программирования (таких как С#), позволяющих разработчикам писать код, устойчивый к изменениям компонентов.
Проблемы управления версиями возникают, когда тип, определенный в одном компоненте (сборке), используется в качестве базового класса для типа другого компонента (сборки). Ясно, что изменения в базовом классе могут повлиять на поведение производного класса. Эти проблемы особенно характерны для полиморфизма, когда в производном типе переопределяются виртуальные методы базового типа.
В С# для типов и/или их членов есть пять ключевых слов, влияющих на управление версиями, причем они напрямую связаны с соответствующими возможностями CLR. В табл. 5.2 показано, как ключевые слова влияют на определение типа или члена типа.

Табл. 5.2. Ключевые слова С# и их влияние на управление версиями компонентов
Ключевое слово С#
Тип
Метод/Свойство/Событие
Константа/Поле

abstract
Экземпляры такого типа создавать нельзя
Член необходимо переопределить и реализовать в производном типе только после этого можно создавать экземпляры производного типа
запрещено

virtual
(запрещено)
Член можно переопределить в производном типе
запрещено

override
(запрещено)
Член переопределяется в производном типе
запрещено

sealed
Тип нельзя использовать в качестве базового
Член нельзя переопределить в производном типе. Это ключевое слово применяется только к методу, переопределяющему виртуальный метод
запрещено

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


Назначение и использование этих ключевых слов будет показано в разделе «Работа с виртуальными методами при управлении версиями типов», но прежде необходимо рассмотреть механизм вызова виртуальных методов в CLR.

Вызов виртуальных методов, свойств и событий в CLR
В этом разделе речь идет только о методах, но все сказанное относится и к виртуальным свойствам и событиям, поскольку они, как будет показано далее, на самом деле реализованы как методы.
Методы содержат код, выполняющий некоторые действия над типом (статические методы) или экземпляром типа (нестатические). У каждого метода есть имя, сигнатура и возвращаемое значение, которое может быть пустым (void). У типа может быть несколько методов с одним именем, но с разным числом параметров или разными возвращаемыми значениями. Можно определить и два метода с одним и тем же именем и параметрами, но с разным типом возвращаемого значения. Однако эта «возможность» большинством языков не используется (за исключением IL) все они требуют, чтобы методы с одинаковым именем различались параметрами, а возвращаемое значение при определении уникальности метода игнорируется. Впрочем, при определении операторов преобразования язык С# смягчает это ограничение (см. главу 8 [11]).
Определим класс Employee с тремя различными видами методов.
internal class Employee
{
// Невиртуальный экземплярный метод
public Int32 GetYearsEmployed() { ... }
// Виртуальный метод (виртуальный, значит, экземплярный)
public virtual String GenProgressReport() { ... }
// Статический метод
public static Employee Lookup(String name) { ... }
}

При компиляции этого кода компилятор помещает три записи в таблицу определений методов сборки. Каждая запись содержит флаги, указывающие, является ли метод экземплярным, виртуальным или статическим.
При компиляции кода, ссылающегося на эти методы, компилятор проверяет флаги в определении методов, чтобы выяснить, какой IL-код нужно вставить для корректного вызова методов. В CLR есть две инструкции для вызова метода:
Инструкция call используется для вызова статических, экземплярных и виртуальных методов. Если с помощью этой инструкции вызывается статический метод, необходимо указать тип, в котором определяется метод. При вызове экземплярного или виртуального метода необходимо указать переменную, ссылающуюся на объект, причем в call подразумевается, что эта переменная не равна null. Иначе говоря, сам тип переменной указывает, в каком типе определен необходимый метод. Если в типе переменной метод не определен, проверяются базовые типы. Инструкция call часто используется для невиртуального вызова виртуального метода.
Инструкция callvirt используется только для вызова экземплярных и виртуальных методов. При вызове необходимо указать переменную, ссылающуюся на объект. Если с помощью этой инструкции вызывается невиртуальный метод экземпляра, тип переменной указывает, где определен необходимый метод. При использовании callvirt для вызова виртуального метода экземпляра CLR определяет настоящий тип объекта, на который ссылается переменная, и вызывает метод полиморфно. При компиляции такого вызова JIT-компилятор генерирует код для проверки значения переменной если оно равно null, CLR сгенерирует исключение NullReferenceException. Из-за этой дополнительной проверки инструкция callvirt выполняется немного медленнее call. Проверка на null выполняется даже при вызове невиртуального метода экземпляра.

Давайте посмотрим, как эти инструкции используются в С#.
using System;
public sealed class Program
{
public static void Main()
{
Console.WriteLine(); // Вызов статического метода
Object obj = new Object();
obj.GetHashCode(); // Вызов виртуального метода экземпляра
obj.GetType(); // Вызов невиртуального метода экземпляра
}
}

Детальное рассмотрение IL-кода см. в главе 6 [11].
Независимо от используемой для вызова экземплярного или виртуального метода инструкции call или callvirt, эти методы всегда в качестве первого параметра получают скрытый аргумент this, ссылающийся на объект, над которым производятся действия.
При проектировании типа следует стремиться минимизировать количество виртуальных методов. Во-первых, виртуальный метод вызывается медленнее невиртуального. Во-вторых, JIT-компилятор не может встраивать (inline) виртуальные методы, что также ударяет по производительности. В-третьих, виртуальные методы затрудняют управление версиями компонентов, как будет показано далее. В-четвертых, при определении базового типа часто создают набор перегруженных методов. Чтобы сделать их полиморфными, лучше всего сделать наиболее сложный метод виртуальным, оставив другие методы невиртуальными. Кстати, следование этому правилу поможет управлять версиями компонентов, не нарушая работу производных типов. Вот пример:
public class Set
{
private Int32 m_length = 0;
// Этот перегруженный метод - невиртуальный
public Int32 Find(Object value)
{ return Find(value, 0, m_length); }
// Этот перегруженный метод - невиртуальный
public Int32 Find(Object value, Int32 startlndex)
{ return Find(value, 0, m_length); }
/* Наиболее функциональный метод сделан виртуальным и может быть
переопределен*/
public virtual Int32 Find(Object value, Int32 startindex,
Int32 endindex)
{
// Здесь находится настоящая реализация, которую можно
// переопределить...
}
// Другие методы.
}

Разумное использование видимости типов и модификаторов доступа к членам – см. [11].

Константы и поля
В этой главе я покажу, как добавить к типу члены, являющиеся данными. В частности, мы рассмотрим константы и поля.
Константы
Константа это идентификатор, значение которого никогда не меняется. При определении идентификатора константы компилятор должен получить его значение во время компиляции. Затем компилятор сохраняет значение константы в метаданных модуля. Это значит, что константы можно определять только для таких типов, которые компилятор считает элементарными. В С# следующие типы считаются элементарными и могут быть использованы для определения констант: Boolean, Char, Byte, SByte, Intl6, UIntl6, Int32, UInt32, Int64, UInt64, Single, Double, Decimal и String.
Другой важный момент: поскольку значение констант никогда не меняется, константы всегда считаются частью типа. Иначе говоря, константы считают статическими, а не экземплярными членами. Определение константы приводит в конечном итоге к созданию метаданных.
Встретив в исходном тексте идентификатор константы, компилятор просматривает метаданные модуля, в котором она определена, извлекает значение константы и внедряет его в генерируемый им IL-код. Поскольку значение константы внедряется прямо в код, в период выполнения память для констант не выделяется. Кроме того, нельзя получать адрес константы и передавать ее по ссылке. Эти ограничения также означают, что изменять значения константы в разных версиях модуля нельзя, поэтому константу надо использовать, только когда точно известно, что ее значение никогда не изменится (хороший пример определение константы MaxIntl6 со значением 32767). Поясню на примере, что я имею в виду.
Возьмем код и скомпилируем его в сборку DLL:
using System;
public sealed class SomeLibraryType
{
/* ПРИМЕЧАНИЕ: С# не позволяет использовать для констант
модификатор static, поскольку всегда подразумевается, что
константы являются статическими*/
public const Int32 MaxEntriesInList = 50;
}

Затем скомпонуем сборку приложения из такого кода:
using System;
public sealed class Program
{
public static void Main()
{
Console.WriteLine("Max entries supported in list: "
+ SomeLibraryType.MaxEntriesInList);
}
}

Нетрудно заметить, что код приложения содержит ссылку на константу MaxEntriesInList. При компоновке этого кода компилятор, обнаружив, что MaxEntriesInList это литерал константы со значением 50, внедрит значение 50 с типом Int32 прямо в IL-код приложения. В сущности, после компоновки кода приложения сборка DLL даже не будет загружаться в период выполнения, поэтому ее можно просто удалить с диска.
Думаю, теперь проблема с управлением версиями при использовании констант должна стать очевидной. Если разработчик изменит значение константы MaxEntriesInList на 1000 и заново опубликует сборку DLL, это не повлияет на код самого приложения. Чтобы в приложении использовалось новое значение константы, его придется перекомпилировать. Нельзя применять константы, если модуль должен задействовать значение, определенное в другом модуле, во время выполнения (а не во время компиляции). В этом случае вместо констант следует использовать неизменяемые поля.
Поля
Поле (field) это член данных, который хранит экземпляр размерного типа или ссылку на ссылочный тип. В табл. 6.1 приведены модификаторы, применяемые по отношению к полям.
Как видно из таблицы, общеязыковая среда (CLR) поддерживает поля как типов (статические), так и экземпляров (нестатические). Динамическая память для хранения поля типа выделяется в пределах объекта-типа, который создается при загрузке типа в домен AppDomain (см. главу 21[11]), что обычно происходит при JIT-компиляции любого метода, ссылающегося на этот тип. Динамическая память для хранения экземплярных полей выделяется при создании экземпляра данного типа.
Табл. 6.1. Модификаторы полей
Термин CLR
Термин С#
Описание

Static
static
Поле является частью состояния типа, а не объекта

Instance
(default)
Поле связано с экземпляром типа, а не самим типом

InitOnly
readonly
Запись в поле разрешается только из кода метода конструктора

Volatile
volatile
Код, обращающийся к полю, не обязательно специально должен оптимизироваться в отношении управления типами компилятором, CLR или оборудованием. Только следующие типы могут объявляться как volatile: все ссылочные типы, Single, Boolean, Byte, SByte, Intl6, UIntl6, Int32, UInt32, Char, а также все перечислимые типы, основанные на следующих типах: Byte, SByte, Intl6, Ulntl6, Int32 или UInt32


Поскольку поля хранятся в динамической памяти, их значения можно получить лишь в период выполнения. Поля также решают проблему с управлением версиями, возникающую при использовании констант. Кроме того, полю можно назначить любой тип данных, поэтому при определении полей можно не ограничиваться встроенными элементарными типами компилятора (что приходится делать при определении констант).
CLR поддерживает изменяемые (read/write) и неизменяемые (readonly) поля. Большинство полей изменяемые. Это значит, что во время исполнения кода значение таких полей может многократно меняться. Однако данные в неизменяемые поля можно записывать только при исполнении метода-конструктора (который вызывается лишь раз при создании объекта). Компилятор и механизм верификации гарантируют, что ни один метод, кроме конструктора, не сможет записать данные в неизменяемое поле. Заметим, что для изменения неизменяемого поля можно задействовать отражение.
Попробуем решить проблему с управлением версиями в примере из раздела «Константы», используя статические неизменяемые поля. Вот новая версия кода сборки DLL:
using System;
public sealed class SomeLibraryType
{
// Модификатор static необходим, чтобы ассоциировать поле с его типом
public static readonly Int32 MaxEntriesInList = 50;
}

Это единственное изменение, которое придется внести в исходный текст, при этом код приложения можно вовсе не менять, но, чтобы увидеть его новые свойства, его придется перекомпилировать. Теперь при исполнении метода Main этого приложения CLR загрузит сборку DLL (так как она требуется во время выполнения) и извлекает значение поля MaxEntriesInList из динамической памяти, выделенной для его хранения. Естественно, это значение будет равно 50.
Допустим, разработчик сборки изменил значение поля с 50 на 1000 и скомпоновал сборку заново. При повторном исполнении код приложения автоматически задействует новое значение 1000. В этом случае не обязательно компоновать код приложения заново он просто работает в том виде, в каком был (хотя и чуть медленнее). Но здесь есть подводный камень: этот сценарий предполагает, что у новой сборки нет строгого имени или что политика управления версиями приложения заставляет CLR загружать именно эту новую версию сборки.
В следующем примере показано, как определять изменяемые статические поля, а также изменяемые и неизменяемые экземплярные поля:
public sealed class SomeType
{
/* Это статическое неизменяемое поле. Его значение рассчитывается и
сохраняется в памяти во время инициализации класса во время
выполнения*/
public static readonly Random s_random = new Random();
// Это статическое изменяемое поле
private static Int32 s_numberOfWrites = 0;
// Это неизменяемое экземплярное поле
public readonly String Pathname = "Untitled";
// Это изменяемое экземплярное поле
private System.IO.FileStream m_fs;
public SomeType(String pathname)
{
/*Эта строка изменяет значение неизменяемого поля.В данном случае
это возможно, так как показанный ниже код расположен в
конструкторе*/
this.Pathname = pathname;
public String DoSomething()
{
/*Эта строка читает и записывает значение статического
изменяемого поля*/
s_numberOfWrites = s_numberOfWrites + 1;
//Эта строка читает значение неизменяемого экземплярного поля
return Pathname;
}
}
}

Многие поля в нашем примере инициализируются при объявлении (inline). C# позволяет использовать этот удобный синтаксис для инициализации констант, а также изменяемых и неизменяемых полей. Как я покажу в главе 8 [11], С# считает, что инициализация поля при объявлении это краткий синтаксис, позволяющий инициализировать поле во время исполнения конструктора. Вместе с тем, в С# возможны проблемы с производительностью, которые нужно учитывать при инициализация поля с использованием синтаксиса встраивания, а не присвоения в конструкторе. Они также обсуждаются в главе 8 [11].
Неизменность поля ссылочного типа означает неизменность ссылки, которую он содержит, но только не объекта, на которую эта ссылка указывает. Вот пример:
public sealed class AType
{
// InvalidChars должно всегда ссылаться на один объект массива
public static readonly Char[] InvalidChars = new Char[] {'А','В','С'};
}
public sealed class AnotherType
{
public static void M()
{
/* Следующие строки кода вполне корректны, компилируются
и успешно изменяют символы в массиве InvalidChars*/
AType.InvalidChars[O] = 'X';
AType.InvalidChars[1] = 'Y';
AType.InvalidChars[2] = 'Z';
/*Следующая строка некорректна и не скомпилируется,
так как то, на что ссылается InvalidChars, изменить нельзя*/
AType.InvalidChars = new Char[] {'Х’,’Y’,'Z’};
}
}
Методы: конструкторы, операторы, преобразования и параметры
В этой главе мы обсудим разновидности методов, которые могут определяться в типе, и разберем ряд вопросов, касающихся методов. В частности, я покажу, как определяют методы-конструкторы (создающие экземпляры типов и сами типы), методы перегрузки операторов и методы операторов преобразования (выполняющие явное и неявное приведение типов). Кроме того, я расскажу, как передать методу параметры ссылками, а также как определить метод, принимающий переменное число параметров.
Конструкторы экземпляров и классы (ссылочные типы)
Конструкторы это специальные методы, позволяющие корректно инициализировать новый экземпляр типа. В таблице определений, входящих в метаданные, методы-конструкторы всегда отмечают сочетанием .ctor (от constructor).
При создании экземпляра объекта ссылочного типа выделяется память для полей данных экземпляра и инициализируются служебные поля (указатель на объект-тип и индекс блока синхронизации SyncBlocklndex), после чего вызывается конструктор экземпляра, устанавливающий исходное состояние нового объекта.
При создании объекта ссылочного типа выделяемая для него память всегда обнуляется до вызова конструктора экземпляра типа. Любые поля, не перезаписываемые конструктором явно, гарантированно содержат 0 или null. В отличие от других методов, конструкторы экземпляра не наследуются.
Иначе говоря, в классе есть экземплярные конструкторы, которые определены в самом классе. Невозможность наследования означает, что к конструктору экземпляра нельзя применить следующие модификаторы: virtual, new, override, sealed и abstract. Если определить класс без явно заданных конструкторов, многие компиляторы (в том числе компилятор С#) создадут конструктор по умолчанию (без параметров), реализация которого просто вызывает конструктор без параметров базового класса.
К примеру, такое определение класса:
public class SomeType { }

идентично определению:
public class SomeType
{
public SomeType() : base() { }
}

Для абстрактных классов компилятор создаст конструктор по умолчанию с модификатором protected, в противном случае область действия будет public. Если в базовом классе нет конструктора без параметров, производный класс должен явно вызвать конструктор базового класса, иначе компилятор вернет ошибку. Для статических классов (sealed и abstract) компилятор не создает конструктор по умолчанию.
В типе может определяться несколько конструкторов, при этом сигнатуры и уровни доступа к конструкторам обязательно должны отличаться. В случае верифицируемого кода конструктор экземпляра должен вызывать конструктор базового класса до обращения к какому-либо из унаследованных от него полей.
Многие компиляторы, включая С#, генерируют вызов конструктора базового класса автоматически, поэтому вам, как правило, об этом можно не беспокоиться. В конечном счете всегда вызывается открытый конструктор объекта System.Object без параметров. Этот конструктор ничего не делает просто возвращает управление по той простой причине, что в System.Object не определено никаких экземплярных полей данных и поэтому конструктору просто нечего делать.
В редких ситуациях экземпляр типа может создаваться без вызова конструктора экземпляра. В частности, метод MemberwiseClone объекта Object выделяет память, инициализирует служебные поля объекта, а затем копирует байты исходного объекта в область памяти, выделенную для нового объекта. Кроме того, конструктор обычно не вызывается при десериализации объекта.
Внимание! Нельзя вызывать какие-либо виртуальные методы конструктора, которые могут повлиять на создаваемый объект. Причина проста: если вызываемый виртуальный метод переопределен в типе, экземпляр которого создается, выполнится реализация производного типа, но к этому моменту еще не завершилась инициализация всех полей в иерархии. В таких обстоятельствах последствия вызова виртуального метода непредсказуемы.

С# предлагает простой синтаксис, позволяющий инициализировать поля во время создания объекта ссылочного типа:
internal sealed class SomeType
{
private Int32 m_x = 5;
}

При создании объекта SomeType его поле т_х инициализируется значением 5. Вы можете спросить: как это происходит? Изучив IL-код метода-конструктора этого объекта (этот метод также фигурирует под именем .ctor), вы увидите код, записывающий в поле m_х значение 5 и вызывающий конструктор базового класса. Иначе говоря, компилятор С# допускает удобный синтаксис, позволяющий инициализировать поля экземпляра при их объявлении. Компилятор транслирует этот синтаксис в метод-конструктор, выполняющий инициализацию. Это значит, что нужно быть готовым к разрастанию кода. Вот пример. Представьте себе такой класс:
internal sealed class SomeType
{
private Int32 m_x = 5;
private String m_s = "Hi there";
private Double m_d = 3.14159;
private Byte m_b;
// Это конструкторы
public SomeType() { ... }
public SomeType(Int32 x) { ... }
public SomeType(String s) { ...; m_d = 10; }
}

Генерируя IL-код для трех методов-конструкторов из этого примера, компилятор помещает в начало каждого из методов код, инициализирующий поля mjx, m_s и m_d. Затем он добавляет к методу код, расположенный внутри методов-конструкторов. Например, IL-код, сгенерированный для конструктора с параметром типа String, состоит из кода, инициализирующего поля mjx, m_s и m_d, и кода, перезаписывающего поле m_d значением 10. Заметьте: поле m_b гарантированно инициализируется значением 0, даже если нет кода, инициализирующего это поле явно.
Поскольку в показанном выше классе определены три конструктора, компилятор трижды генерирует код, инициализирующий поля т_х, m_s и m_d: по разу для каждого из конструкторов. Если имеется несколько инициализируемых экземплярных полей и множество перегруженных методов-конструкторов, стоит подумать о том, чтобы определить поля, не инициализируя их; создать единственный конструктор, выполняющий общую инициализацию и заставить каждый метод-конструктор явно вызывать конструктор, выполняющий общую инициализацию.
Этот подход позволит уменьшить размер генерируемого кода. Вот пример использования способности С# явно заставлять один конструктор вызывать другой конструктор за счет использования зарезервированного слова this:
internal sealed class SomeType
{
// Здесь нет кода, явно инициализирующего поля
private Int32 m_x;
private String m_s;
private Double m_d;
private Byte m_b;
/* Этот конструктор содержит код, инициализирующий поля
значениями по умолчанию. Он должен вызываться всеми остальными
конструкторами*/
public SomeType()
{
m_x = 5;
m_s = "Hi there";
m_d = 3.14159;
m_b = Oxff;
}
/* Этот конструктор инициализирует поля значениями по умолчанию,
а затем изменяет значение m_х*/
public SomeType(Int32 x) : this()
{
m_х = х;
}
/* Этот конструктор инициализирует поля значениями по умолчанию,
а затем изменяет значение m_s*/
public SomeType(String s) : this()
{
m_s = s;
}
/* Этот конструктор инициализирует поля значениями по умолчанию,
а затем изменяет значение m_х и m_s*/
public SomeType(Int32 x, String s) : this()
{
m_x = x;
m_s = s;
}
}

Конструкторы экземпляров и структуры (значимые типы)
Конструкторы значимых типов {struct) работают иначе, чем конструкторы ссылочных типов {class). CLR всегда разрешает создание экземпляров значимых типов, и этому ничто не может помешать. Поэтому, по большому счету, конструкторы у значимого типа можно не определять. Фактически многие компиляторы (включая С#) не определяют для значимых типов конструкторы по умолчанию, не имеющие параметров. Разберем такой код:
internal struct Point
{
public Int32 m_x, m_y;
}
internal sealed class Rectangle
{
public Point m_topLeft, m_bottomRight;
}

Чтобы создать объект Rectangle, надо использовать оператор new, указав конструктор. В этом случае вызывается конструктор, автоматически сгенерированный компилятором С#. Память, выделенная для объекта Rectangle, включает место для двух экземпляров значимого типа Point. Из соображений повышения производительности CLR не пытается вызвать конструктор для каждого экземпляра значимого типа, содержащегося внутри объекта ссылочного типа. Но, как сказано выше, поля значимого типа инициализируются нулевыми или пустыми значениями.
CLR действительно позволяет программистам определять конструкторы для значимых типов, но эти конструкторы будут выполнены лишь при наличии кода, явно вызывающего один из них, например, как в конструкторе объекта Rectangle:
internal struct Point
{
public Int32 m_x, m_y;
public Point(Int32 x, Int32 y)
{
m_x = x;
m_y = y;
}
}
internal sealed class Rectangle
{
public Point m_topLeft, m_bottomRight;
public Rectangle()
{
/* В С# оператор new, использованный для создания экземпляра
значимого типа, вызывает конструктор для инициализации
полей значимого типа*/
m_topleft = new Point(1, 2);
m_bottomRight = new Point(100, 200);
}
}

Конструктор экземпляра значимого типа будет исполнен, только если вызвать его явно. Так что, если конструктор объекта Rectangle не инициализировал его поля mtopLeft и mbottomRight вызовом конструктора Point оператором new, поля m_х и m_у у обеих структур Point будут содержать 0.
Если значимый тип Point уже определен, то конструктор по умолчанию, не имеющий параметров, не определяется. Но давайте перепишем наш код:
internal struct Point
{
public Int32 m_x, m_y;
public Point()
{
m_x = m_y = 5;
}
}
internal sealed class Rectangle
{
public Point m_topLeft, m_bottomRight;
public Rectangle() { }
}

А теперь скажите: какими значениями 0 или 5 будут инициализированы поля m_х и m_y, принадлежащие структурам Point (m_topLeft и m_bottomRight)? (Предупреждаю: вопрос с подвохом.)
Многие разработчики (особенно с опытом программирования на C++) будут ожидать, что компилятор С# поместит в конструктор Rectangle код, автоматически вызывающий конструктор структуры Point по умолчанию, не имеющий параметров, для двух полей Rectangle. Но, чтобы увеличить быстродействие приложения во время выполнения, компилятор С# не сгенерирует такой код автоматически. Фактически большинство компиляторов никогда не генерирует автоматически код для вызова конструктора по умолчанию для значимого типа, даже если у него есть конструктор без параметров. Чтобы принудительно исполнить конструктор значимого типа, не имеющий параметров, разработчик должен добавить код для явного вызова конструктора значимого типа.
С учетом сказанного можно ожидать, что поля m_x и m_y обеих структур Point из объекта Rectangle в показанном выше коде будут инициализированы нулевыми значениями, так как в этой программе нет явного вызова конструктора Point.
Однако, как я сказал, мой первый вопрос был с подвохом. Подвох в том, что С# не позволяет определять для значимого типа конструкторы без параметров. Поэтому показанный выше код на самом деле даже не компилируется. При попытке скомпилировать его компилятор С# генерирует сообщение об ошибке: «error CSO568: Structs cannot contain explicit parameterless constructors» («ошибка CSO568: структура не может содержать явные конструкторы без параметров»).
Примечание. Строго говоря, в поля значимого типа обязательно заносятся значения 0 или null, если значимый тип является вложенным в объект ссылочного типа. Однако где гарантия, что поля значимых типов, работающих со стеком, будут инициализированы значениями 0 или null! Чтобы код был верифицируемым, перед чтением любого поля значимого типа, работающего со стеком, нужно записать в него значение. Если код сможет прочитать значение поля значимого типа до того, как туда будет записано какое-то значение, может нарушиться безопасность. С# и другие компиляторы, генерирующие верифицируемый код, гарантируют, что поля любых значимых типов, работающие со стеком, перед чтением обнуляются или в них хотя бы записываются некоторые значения. Поэтому при верификации во время выполнения исключение сгенерировано не будет. Но обычно можно предполагать, что поля значимых типов инициализируются нулевыми значениями, и все сказанное в этом примечании можно полностью игнорировать.
С# преднамеренно запрещает определять конструкторы без параметров у значимых типов, чтобы не вводить разработчиков в заблуждение относительно того, какой конструктор вызывается. Если конструктор определить нельзя, компилятор никогда не будет автоматически генерировать код, вызывающий такой конструктор. В отсутствие конструктора без параметров поля значимого типа всегда инициализируются нулевыми или пустыми значениями.

Хотя С# не допускает использования значимых типов с конструкторами без параметров, это допускает CLR. Так что, если вас не беспокоят скрытые особенности работы системы, описанные выше, можно на другом языке (например, на IL) определить собственный значимый тип с конструктором без параметров.
Поскольку С# не допускает использования значимых типов с конструкторами без параметров, при компиляции следующего типа компилятор сообщает об ошибке: «error CSO573: 'SomeValType.m_x': cannot have instance field initializers in structs» (нельзя создавать конструкторы экземплярных полей в структурах).
internal struct SomeValType
{
// В значимом типе нельзя встраивать инициализацию экземплярных полей
private Int32 m_x = 5;
}

Кроме того, поскольку верифицируемый код перед чтением любого поля значимого типа требует записывать в него какое-либо значение, любой конструктор, определенный для значимого типа, должен инициализировать все поля этого типа.
Следующий тип определяет конструктор для значимого типа, но не может инициализировать все его поля:
internal struct SomeValType
{
private Int32 m_x, m_y;
// C# допускает наличие у значимых типов конструкторов с параметрами,
public SomeValType(Int32 x)
{
m_х = х;
// Обратите внимание: поле m_у здесь не инициализируется
}
}

При компиляции этого типа компилятор С# генерирует сообщение об ошибке: «error CSO171: Field 'SomeVallype.m_y' must be fully assigned before control leaves the constructor» (поле 'SomeValType.m_y' должно быть полностью определено до возвращения управления конструктором). Чтобы разрешить эту проблему, в поле m_y надо занести значение (обычно это 0) в конструкторе.
Конструкторы типов
Помимо конструкторов экземпляров, CLR поддерживает конструкторы типов (также известные как статические конструкторы, конструкторы классов или инициализаторы типов). Конструкторы типа можно применять и к интерфейсам (хотя С# этого не допускает), ссылочным и значимым типам. Подобно тому, как конструкторы экземпляров используются для установки первоначального состояния экземпляра типа, конструкторы типов применяются для установки первоначального состояния типа. По умолчанию у типа не определен ни один конструктор. У типа может быть один и только один конструктор. Кроме того, у конструкторов типа никогда не бывает параметров. Вот как определяются ссылочные и значимые типы с конструкторами в программах на С#:
internal sealed class SomeRefType
{
static SomeRefType()
{
// Исполняется при первом обращении к ссылочному типу SomeRefType
}
}
internal struct SomeValType
{
/* С# на самом деле допускает определять для значимых типов
конструкторы, не имеющие параметров*/
static SomeValType()
{
// Исполняется при первом обращении к значимому типу SomeValType
}
}

Заметьте: конструкторы типа определяют так же, как конструкторы экземпляров, не имеющие параметров, за исключением того, что их помечают как статические. Кроме того, конструкторы типа всегда должны быть закрытыми (С# делает их закрытыми автоматически). Но, если явно пометить в исходном тексте программы конструктор типа как закрытый (или как-то иначе), компилятор С# выведет сообщение об ошибке. Конструкторы типа всегда должны быть закрытыми, чтобы код разработчика не смог их вызвать, напротив, CLR всегда способна вызвать конструктор типа.

Внимание! Хотя конструктор типа можно определить в значимом типе, этого никогда не следует делать, так как иногда CLR не вызывает статический конструктор значимого типа. Вот пример:
internal struct SomeValType
{
static SomeValType()
{
Console.WriteLine("This never gets displayed");
}
public Int32 m_x;
}

public sealed class Program
{
public static void Main()
{
SomeValType[] a = new SomeValType[10];
a[0].m_x = 123;
Console.WriteLine(a[O].m_x); // Отображаем 123

}
}

Есть определенные особенности вызова конструктора типа. При компиляции метода JIT-компилятор обнаруживает типы, на которые есть ссылки из кода. Если в каком-либо из типов определен конструктор, JIT-компилятор проверяет, был ли исполнен конструктор типа в данном домене AppDomain. Если нет, JIT-компилятор создает в IL-коде вызов конструктора типа. Если же код уже исполнялся, JIT-компилятор вызова конструктора типа не создает, так как «знает», что тип уже инициализирован. (Пример подобного поведения см. в разделе «Производительность конструкторов типа».)
Примечание. Поскольку CLR гарантирует, что конструктор типа выполняется только однажды в каждом домене AppDomain, а также обеспечивает его безопасность по отношению к потокам, конструктор типа лучше всего подходит для инициализации всех Singleton-объектов, необходимых для существования типа.
Далее, после JIT-компиляции метода, начинается выполнение потока, и в конечном итоге очередь доходит до выполнения кода вызова конструктора. В реальности, может оказаться, что несколько потоков одновременно начнут выполнять метод. CLR стремится обеспечить, чтобы конструктор типа выполнялся только раз в каждом домене AppDomain. Для этого при вызове конструктора типа вызывающий поток получает исключающую блокировку синхронизации потоков. Поэтому, если несколько потоков одновременно попытаются вызывать конструктор типа, только один получит блокировку, а остальные блокируются. Первый поток выполнит код статического конструктора. После выхода из конструктора первого потока, «проснутся» простаивающие потоки и проверят, был ли выполнен конструктор. Они не станут снова выполнять код, а просто выполнят возврат управления из метода конструктора. Кроме того, при последующем вызове какого-либо из этих методов CLR будет «в курсе», что конструктор типа уже выполнялся, и предотвратит еще одно его выполнение.
Наконец, если конструктор типа генерирует необрабатываемое исключение, CLR считает такой тип непригодным. При попытке обращения к любому полю или методу такого типа возникает исключение System.TypelnitializationException.
Код конструктора типа может обращаться только к статическим полям типа, обычно это делается, чтобы инициализировать их. Как и в случае экземплярных полей, С# предлагает простой синтаксис, позволяющий инициализировать статические поля типа:
internal sealed class SomeType
{
private static Int32 s_x = 5;
}
Примечание С# не позволяет в значимых типах использовать встроенный синтаксис инициализации полей, но разрешает это в статических полях. Иначе говоря, если изменить приведенный выше тип с class на struct, код прекрасно скомпилируется и будет работать, как задумано.

При компоновке этого кода компилятор автоматически генерирует конструктор типа SomeType. Иначе говоря, получается тот же эффект, как если бы этот код был исходно написан так:
internal sealed class SomeType
{
private static Int32 s_x;
static SomeType() { s_x = 5; }
}

Конструктор типа не должен вызывать конструктор базового класса. Этот вызов необязателен, так как ни одно статическое поле типа не используется совместно с базовым типом и не наследуется от него.

Примечание. Ряд языков, таких как Java, ожидает, что при обращении к типу будет вызван его конструктор, а также конструкторы всех его базовых типов. Кроме того, интерфейсы, реализованные этими типами, тоже должны вызывать свои конструкторы. CLR не поддерживает такую семантику, но позволяет компиляторам и разработчикам предоставлять поддержку подобной семантики через метод RunClassConstructor, поддерживаемыйтипом SystemRuntime.CompilerServicesRuntimeHelpers. Компилятор любого языка, требующего подобную семантику, генерирует в конструкторе типа код, вызывающий этот метод для всех базовых типов. При использовании метода RunClassConstructor для вызова конструктора типа CLR определяет, был ли он исполнен ранее, и если да, то не вызывает его снова.

В завершение этого раздела рассмотрим код:
internal sealed class SomeType
{
private static Int32 s_x = 5;
static SomeType()
{ s_x = 10;}
}

Здесь компилятор С# генерирует единственный метод-конструктор типа, который сначала инициализирует поле s_x значением 5, затем значением 10. Иначе говоря, при генерации IL-кода конструктора типа компилятор С# сначала генерирует код, инициализирующий статические поля, затем обрабатывает явный код, содержащийся внутри метода-конструктора типа.

Методы перегруженных операторов
В некоторых языках тип может определять, как операторы должны манипулировать его экземплярами. В частности, многие типы (например, System.String) используют перегрузку операторов равенства (==) и неравенства (!=). CLR ничего не известно о перегрузке операторов ведь она даже не знает, что такое оператор. Смысл знаков операторов и код, который должен быть сгенерирован, когда этот знак встречается в исходном тексте, определяется языком программирования.
Так, если в С#-программе поставить между обычными числами знак «+», компилятор сгенерирует код, выполняющий сложение двух чисел. Когда знак «+» применяют к строкам, компилятор С# генерирует код, выполняющий конкатенацию этих строк. Для обозначения неравенства в С# используется знак «!=», а в Visual Basic «<>». Наконец, знак «^» в С# означает операцию «исключающее или» (XOR), тогда как в Visual Basic это возведение в степень.
Хотя CLR находится в неведении относительно операторов, она не регламентирует, как языки программирования должны поддерживать перегрузку операторов. Смысл в том, чтобы без труда использовать перегрузку при написании кода на разных языках. В случае каждого конкретного языка принимается отдельное решение, будет ли этот язык поддерживать перегрузку операторов и, если да, какой синтаксис будет задействован для представления и использования перегруженных операторов. С точки зрения CLR перегруженные операторы представляют собой просто методы.
От выбора языка зависит наличие поддержки перегруженных операторов и их синтаксис, а при компиляции исходного текста компилятор генерирует метод, определяющий работу оператора. Спецификация CLR требует, чтобы перегруженные методы оператора были открытыми и статическими. Дополнительно С# (и многие другие языки) требует, чтобы у оператора-метода тип по крайней мере одного из параметров или возвращаемого значения совпадал с типом, в котором определен оператор-метод. Причина этого ограничения в том, что это позволяет компилятору С# в разумное время находить кандидатуры операторов-методов для привязки.
Вот пример метода перегруженного оператора, заданного в определении класса С#:
public sealed class Complex
{
public static Complex operator+(Complex c1, Complex c2) { ... }
}

Компилятор генерирует определение метода op_Addition и устанавливает в записи с определением этого метода флаг specialname, свидетельствующий о том, что это «особый» метод. Когда компилятор языка (в том числе компилятор С#) видит в исходном тексте оператор «+», он исследует типы его операндов. При этом компилятор пытается выяснить, не определен ли для одного из них метод op_Addition с флагом specialname, параметры которого совместимы с типами операндов.
Если такой метод существует, компилятор генерирует код, вызывающий этот метод, иначе возникает ошибка компиляции.
В табл. 8-1 и 8-2 приводится набор унарных и бинарных операторов, которые С# позволяет перегружать, их знаки и рекомендованные имена соответствующих методов, которые должен генерировать компилятор. Третий столбец я прокомментирую в следующем разделе.
Табл. 7.1. Унарные операторы С# и CLS-совместимые имена соответствующих методов
Знак оператора С#
Имя специального метода
Рекомендуемое CLS-совместимое имя метода

+
op_UnaryPlus
Plus

-
op_UnaryNegation
Negate

!
op_LogicalNot
Not

~
op_OnesComplement
OnesComplement

++
op_Increment
Increment

--
op_Decrement
Decrement

Нет
op_True
IsTrue {get;}

Нет
op_False
IsFalse { get; j


Табл. 7.2. Бинарные операторы С# и CLS-совместимые имена соответствующих методов
Знак оператора С#
Имя специального метода
Рекомендуемое CLS-совместимое имя метода

+
op_Addition
Add

-
op_Subtraction
Subtract

*
op_Multiply
Multiply

/
op_Division
Divide

%
op_Modulus
Mod

&
op_BitwiseAnd
BitwiseAnd

|
op_BitwiseOr
BitwiseOr

^
op_ExclusiveOr
Xor

<<
op_LeftSMft
LeftShift

>>
op_RightShift
RightShift

==
op_Equality
Equals

!=
op_Inequality
Compare

<
op_LessThan
Compare

>
op_GreaterThan
Compare

<=
op_LessThanOrEqual
Compare

>=
op_GreaterThanOrEqual
Compare


В спецификации CLR определены многие дополнительные операторы, поддающиеся перегрузке, но С# их не поддерживает. Они не очень распространены, поэтому я их здесь не указал. Полный список см. в спецификации ЕСМА (www.ecma-international.org/publications/standards/Ecma-335.htm) общеязыковой инфраструктуры СП, разделы 10.3.1 (унарные операторы) и 10.3.2 (бинарные операторы).
Примечание Если изучить фундаментальные типы библиотеки классов .NET Framework (FCL) Int32, Int64, UInt32 и т. д. можно заметить, что они не определяют методы перегруженных операторов. Дело в том, что CLR поддерживает IL-команды, позволяющие манипулировать экземплярами этих типов. Если бы эти типы поддерживали соответствующие методы, а компиляторы генерировали вызывающий их код, то каждый такой вызов снижал бы быстродействие во время выполнения. Кроме того, чтобы выполнить ожидаемое действие, такой метод все равно исполнял бы те же инструкции языка IL Для вас это означает следующее: если язык, на котором вы пишете, не поддерживает какой-либо из фундаментальных типов FCL, вы не сможете выполнять действия над экземплярами этого типа.
Свойства
Свойства позволяют обращаться к методу в исходном тексте программы, используя упрощенный синтаксис. CLR поддерживает два вида свойств: без параметров, их называют просто свойства, и с параметрами у них в разных языках разное название. Например, в С# свойства с параметрами называют индексаторами, а в Visual Basic свойствами по умолчанию.
Свойства без параметров
Многие типы определяют сведения о состоянии, которые можно извлечь или изменить. Часто эти сведения о состоянии реализуют в виде таких членов типа, как поля. Вот, например, определение типа с двумя полями:
public sealed class Employee
{
public String Name; // Имя сотрудника.
public Int32 Age; // Возраст сотрудника.
}

Создавая экземпляр этого типа, можно получить или определить любые сведения о его состоянии при помощи такого примерно кода:
Employee e = new EmployeeO;
e.Name = "Jeffrey Richter"; // Определить имя сотрудника.
e.Age = 41; // Определить возраст сотрудника.
Console.WriteLine(e.Name); // Выводим на экран "Jeffrey Richter"

Этот способ запроса и определения информации о состоянии объекта очень распространен. Но я готов спорить, что предыдущий код ни в коем случае не следует писать так, как в примере. Одним из соглашений объектно-ориентированного программирования и разработки является инкапсуляция данных. Инкапсуляция данных означает, что поля типа ни в коем случае не следует открывать для общего пользования, так как в этом случае слишком просто написать код, способный испортить сведения о состоянии объекта путем ненадлежащего применения полей. Например, таким кодом разработчик может легко повредить объект Employee:
e.Age = -5; // Можете вообразить человека, которому -5 лет?

Есть и другие причины инкапсуляции доступа к полям данных типа. Допустим, вам нужен доступ к полю, чтобы что-то сделать, разместить в кеше некоторое значение или выполнить отложенное создание какого-то внутреннего объекта, при этом обращение к полю не должно нарушать безопасность потоков. Или, скажем, поле это логическое поле, значение которого представлено не байтами в памяти, а вычисляется по некоторому алгоритму.
Любая из этих причин заставляет меня рекомендовать при разработке типов, во-первых, помечать все поля как закрытые, и, во-вторых, чтобы дать пользователю вашего типа возможность получения и определения сведений о состоянии, следует создавать специальные методы, которые служат именно этой цели.
Методы, выполняющие функции оболочки для доступа к полю, обычно называют аксессорами (accessor). Аксессоры могут выполнять дополнительную «зачистку», гарантируя, что сведения о состоянии объекта не нарушатся. Я переписал класс из примера так:
public sealed class Employee
{
private String m_Name; // Поле стало закрытым,
private Int32 m_Age; // Поле стало закрытым.
public String GetName { return(m_Name); }
public void SetName(String value) { m_Name = value; }
public Int32 GetAge() { return(m_Age); }
public void SetAge(Int32 value)
{
if (value < 0) throw new ArgumentOutOfRangeException("value",
value.ToString(), "The value must be greater than"+
" or equal to 0");
m_Age = value;
}
}

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

e.SetName("Jeffrey Richter"); // Обновление имени сотрудника
String EmployeeName = e.GetName(); // Получение возраста сотрудника
e.SetAge(41); // Обновление возраста сотрудника
e.SetAge(-5); // Генерируется исключение: ArgumentOutOfRangeException
Int32 EmployeeAge = e.GetAge(); // Получение возраста сотрудника

Лично я считаю эти недостатки незначительными. И все же CLR поддерживает механизм, частично компенсирующий первый недостаток и полностью устраняющий второй. Этот механизм свойства.
Этот класс Employee функционально идентичен показанному выше, но в нем используются свойства:
public sealed class Employee
{
private String m_Name;
private Int32 m_Age;
public String Name // Это свойство
{
get {return m_Name;}
set {m_Name = value;} /* Ключевое слово value всегда
идентифицирует новое значение */
}
public Int32 Age // И это свойство тоже
{
get { return(m_Age); }
set
{
if (value < 0) /* Ключевое слово value всегда
идентифицирует новое значение*/
throw new ArgumentOutOfRangeException("value",
value.ToString(),
"The value must be greater than or equal to 0");
m_Age = value;
}
}
}

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

e.Name = "Jeffrey Richter"; // "Задать" имя сотрудника
String EmployeeName = e.Name; // "Получить" имя сотрудника
е.Age =41; // "Задать" возраст сотрудника
е.Age = -5; /* Генерируется исключение
ArgumentOutOfRangeException */
Int32 EmployeeAge = е.Аgе; // "Получить" возраст сотрудника.

Можно считать свойства «умными» полями, то есть полями с дополнительной логикой. CLR поддерживает статические, экземплярные, абстрактные и виртуальные свойства. Кроме того, свойства могут помечаться модификатором доступа (см. о них главу 6 работы [11]) и определяться в интерфейсах (см. главу 14 работы [11]).
У каждого свойства есть имя и тип (который не может быть void). Нельзя перегружать свойства (то есть определять несколько свойств с одинаковыми именами, но с разным типом). Определяя свойство, обычно определяют пару методов: get и set. Однако, опустив set, можно определить свойство, доступное только для чтения, а если оставить только get, получится свойство, доступное только для записи.
Методы get и set свойства довольно часто манипулируют закрытым полем, определенным в типе. Это поле обычно называют вспомогательным полем (backing field). Однако методам get и set не приходится обращаться к вспомогательному полю.
Так, тип System.Threading.Thread поддерживает свойство Priority, взаимодействующее непосредственно с ОС, а объект Thread не поддерживает поле, хранящее приоритет потока. Другой пример свойств, не имеющих вспомогательных полей, неизменяемые свойства, вычисляемые при выполнении: длина массива, заканчивающегося нулем, или область прямоугольника, заданного шириной и высотой, и т. д.
При определении свойства компилятор генерирует и помещает в результирующий управляемый модуль:
метод-аксессор get этого свойства генерируется, только если для свойства определен аксессор get;
метод-аксессор set этого свойства генерируется, только если для свойства определен аксессор set;
определение свойства в метаданных управляемого модуля генерируется всегда.

Вернемся к показанному выше типу Employee. При его компиляции компилятор обнаруживает свойства Name и Age. Поскольку у обоих есть методы-аксессоры get и set, компилятор генерирует в типе Employee четыре определения методов. Результат получается такой, как если бы тип был исходно написан так:
public sealed class Employee
{
private String m_Name;
private Int32 m_Age;
public String get_Name(){ return m_Name; }
public void set_Name(String value)
{
m_Name = value; /* Аргумент value всегда идентифицирует
новое значение*/
}
public Int32 get_Age() { return m_Age; }
public void set_Age(Int32 value)
{
if (value < 0) /*value всегда идентифицирует новое значение*/
{
throw new ArgumentOutOfRangeException("value",
value.ToString{},
"The value must be greater than or equal to 0");
}
m_Age = value;
}
}

Компилятор автоматически генерирует имена этих методов, прибавляя приставку get_ или set_ к имени свойства, заданному разработчиком.
В С# есть встроенная поддержка свойств. Обнаружив код, пытающийся получить или определить свойство, компилятор на самом деле генерирует вызов соответствующего метода. Если используемый язык не поддерживает свойства напрямую, к ним все равно можно обращаться через вызов нужного аксессора. Эффект тот же, только исходный текст выглядит менее изящным.
Помимо аксессоров, для каждого из свойств, определенных в исходном тексте, компиляторы генерируют в метаданных управляемого модуля запись с определением свойства. Такая запись содержит несколько флагов и тип свойства, а также ссылки на аксессоры get и set. Эта информация существует лишь затем, чтобы провести связь между абстрактным понятием «свойства» и его методами-аксессорами. Компиляторы и другие инструменты могут использовать эти метаданные, которые можно получить через класс SystemReflectionPropertylnfo. И все же CLR не использует эти метаданные, требуя при выполнении только методы-аксессоры.
Свойства с параметрами
У свойств, рассмотренных в предыдущем разделе, аксессоры get не принимали параметры. Поэтому я называю их свойствами без параметров (parameterless properties). Они проще, так как их использование по ощущениям напоминает обращение к полю. Помимо таких «полеобразных» свойств, языки программирования поддерживают то, что я называю свойствами с параметрами (parameterful properties), у которых аксессоры get принимают один или несколько параметров.
Разные языки поддерживают свойства с параметрами по-разному. Кроме того, в разных языках свойства с параметрами называют по-разному: в С# индексаторы, а в Visual Basic свойства по умолчанию. Здесь я остановлюсь на поддержке индексаторов в С# при помощи свойств с параметрами.
В С# синтаксис поддержки свойств с параметрами (индексаторов) напоминает синтаксис массивов. Иначе говоря, можно представить индексатор как способ, позволяющий разработчику на С# перегружать оператор []. Вот пример типа BitArray, который позволяет индексировать набор битов, поддерживаемый экземпляром типа, используя синтаксис массива:
using System;
public sealed class BltArray
{
// Закрытый массив байт, хранящий биты,
private Byte[] m_byteArray;
private Int32 m_numBits;
/* Конструктор, выделяющий память для массива байт
и устанавливающий все биты равными 0.*/
public BitArray(Int32 numBits)
{
// Сохранить число битов,
if (numBits <= 0) throw new ArgumentOutOfRangeException
("numBits",numBits. ToString(),"numBits must be > 0");
// Сохранить число битов.
m_numBits = numBits;
// Выделить байты для массива битов.
m_byteArray = new Byte[(numBits + 7) / 8];
// Это индексатор (свойство с параметрами),
public Boolean this[Int32 bitPos]
{
// Это метод-аксессор get индексатора,
get {
// Сначала нужно проверить аргументы.
if ((bitPos < 0) || (bitPos >= m_numBits))
throw new ArgumentOutOfRangeException("bitPos");
// Вернуть состояние индексируемого бита.
return (m_byteArray[bitPos/8] & (1 « (bitPos % 8))) != 0;
}
// Это метод-аксессор set индексатора,
set {
if ((bitPos < 0) || (bitPos >= m_numBits))
throw new ArgumentOutOfRangeException("bitPos",
bitPos.ToString());
if (value)
{
// Включить индексируемый бит.
m_byteArray[bitPos / 8] = (Byte)
(m_byteArray[bitPos / 8] | (1 « (bitPos % 8)));
}
else
{
// Выключить индексируемый бит.
m_byteArray[bitPos / 8] = (Byte)
(m_byteArray[bitPos / 8] & ~(1 « (bitPos % 8)));
}
}
}

Использовать индексатор типа BitArray невероятно просто:
// Выделить массив BitArray, который может хранить 14 битов.
BitArray ba = new BitArray(14);
// Включить все четные биты, вызвав аксессор set.
for (Int32 x = 0; х < 14; х++)
{
ba[x] = (х % 2 == 0);
}
// Показать состояние всех битов, вызвав аксессор get.
for (Int32 x = 0; х < 14; х++)
{
Console.WriteLine("Bit " + x + " is " + (ba[x] ? "On" : "Off"));
}

В типе BitArray индексатор принимает один параметр типа Int32 bitPos. У каждого индексатора должен быть хотя бы один параметр, но параметров может быть и больше. Тип параметров (как и тип возвращаемого значения) может быть любым.
Индексаторы довольно часто создают для поиска значений в ассоциативном массиве. Действительно, тип System.CollectionsHashtable предлагает индексатор, который принимает ключ и возвращает связанное с ключом значение. В отличие от свойств без параметров тип может поддерживать множество перегруженных индексаторов при условии, что их сигнатуры различны.
Подобно аксессору set свойства без параметров, аксессор set индексатора также содержит скрытый параметр (в С# его называют value), который указывает новое значение, желаемое для «индексируемого элемента».
CLR не различает свойства без параметров и с параметрами. Для нее любое свойство это всего лишь пара методов, определенных внутри типа. Как сказано выше, в различных языках синтаксис создания и использования свойств с параметрами различны. Использование для индексатора в С# конструкции this[...] чистый произвол создателей языка, означающий, что в С# допускается определять индексаторы только на экземплярах объектов. В С# нет синтаксиса, позволяющего разработчику определять статистическое свойство-индексатор напрямую, хотя на самом деле CLR поддерживает статические свойства с параметрами.
Поскольку CLR обрабатывает свойства с параметрами и без них одинаково, компилятор генерирует в результирующем управляемом модуле те же три элемента:
метод-аксессор set свойства с параметрами генерируется, только если у свойства определен аксессор set;
метод-аксессор get свойства с параметрами генерируется, только если у свойства определен аксессор get;
определение свойства в метаданных управляемого модуля генерируется всегда; в метаданных нет отдельной таблицы для хранения определений свойств с параметрами: ведь для CLR свойства с параметрами просто свойства.

Компиляция индексатора типа BitArray, показанного выше, происходит так, как если бы он исходно был написан так:
public sealed class BitArray
{
// Это метод-аксессор get индексатора.
public Boolean get_Item(Int32 bitPos) { /* ... */ }
// Это метод-аксессор set индексатора.
public void set_Item(Int32 bitPos, Boolean value) {/*... */ }
}
Компилятор автоматически генерирует имена для этих методов, добавляя к Item префикс get_ или set_. Поскольку синтаксис индексаторов в С# не позволяет разработчику задавать имя, создателям компилятора С# пришлось выбирать имя методов-аксессоров, и они выбрали Item. Поэтому имена созданных компилятором методов get_item и set_item.
Изучая справочник .NET Framework Reference, достаточно найти свойство Item, чтобы сказать, что данный тип поддерживает индексатор. Так, тип System.Collections.GenericList предлагает открытое свойство-экземпляр Item, которое является индексатором объекта List.
Программируя на С#, вы никогда не увидите имя Item, поэтому выбор его компилятором обычно не должен вызывать беспокойства. Но, если вы конструируете индексатор типа, который будет доступен программам, написанным на других языках, возможно, придется изменить имена аксессоров индексатора (get и set).
С# позволяет переименовать эти методы, применив к индексатору пользовательский атрибут SystemRuntime.CompilerServicesIndexerNameAttribute. Например:
using System;
using System.Runtime.CompilerServices;
public sealed class BitArray
{
[IndexerName("Bit")]
public Boolean this[Int32 bitPos]
{
// Здесь определен по крайней мере один метод-аксессор.
}
}

Теперь компилятор сгенерирует вместо методов get_item и set_item методы get_Bit и set_Bit. Во время компиляции компилятор С# обнаруживает атрибут IndexerName и узнает, как именовать метаданные методов и свойств; сам по себе атрибут не создается в метаданных сборки.1
В С# в одном типе можно определять несколько индексаторов при условии, что они принимают разные наборы параметров. В других языках программирования атрибут IndexerName позволяет определять несколько индексаторов с одинаковой сигнатурой, поскольку их имена могут отличаться. Однако С# не допускает этого, так как принятый в нем синтаксис не ссылается на индексатор по имени, а значит, компилятор не будет знать, на какой индексатор ссылаются. Попытка компиляции следующего исходного текста на С# заставляет компилятор генерировать сообщение об ошибке «error CSO111: Class 'SomeType' already defines a member called 'this' with the same parameter types» («ошибка CSO111: в классе 'SomeType' уже определен член 'this' с таким же типом параметра»).
using System;
using System.Runtime.CompilerServices;
public sealed class SomeType
{
// Определяем метод-аксессор get_Item.
public Int32 this[Boolean b]
{
get { return 0; }
}
// Определяем метод-аксессор get_Jeff.
[IndexerName("Jeff")]
public String this[Boolean b]
{
get { return null; }
}
}
Как видите, С# представляет себе индексаторы как способ перегрузки оператора [], и этот оператор не позволяет различать свойства с одинаковыми наборами параметров, но различающиеся лишь именами аксессоров.
Кстати, примером типа с измененным именем индексатора может быть System.String, в котором имя индексатора String Chars, а не Item. Это свойство позволяет получать отдельные символы из строки. Было принято решение, что для языков программирования, не использующих синтаксис с оператором [] для вызова этого свойства, имя Chars будет более информативно.
Выбор главного свойства с параметрами
При анализе ограничений, которые С# налагает на индексаторы, возникают два вопроса.
Что, если язык, на котором написан тип, позволяет разработчику определять несколько свойств с параметрами?
Как задействовать этот тип в С#-программе?

Ответ: в этом типе надо выбрать один из методов среди свойств с параметрами и сделать его свойством по умолчанию, применив к самому классу экземпляр System.Reflection.DefaultMemberAttribute. Кстати, DefaultMemberAttribute можно применять к классам, структурам или интерфейсам. В С# при компиляции типа, определяющего свойства с параметрами, компилятор автоматически применяет к определяющему типу экземпляр DefaultMemberAttribute и учитывает его при использовании DefaultMemberAttribute.
Конструктор этого атрибута задает имя, которое будет назначено свойству с параметрами, выбранному как свойство по умолчанию для этого типа.
Итак, в случае типа С#, у которого определено свойство с параметрами, но нет атрибута IndexerName, атрибут DefaultMember, которым помечен определяющий тип, будет указывать имя Item. Если применить к свойству с параметрами атрибут IndexerName, то атрибут DefaultMember определяющего типа будет указывать на строку, заданную атрибутом IndexerName. Помните: С# не будет компилировать код, содержащий свойства с параметрами, имеющие разные имена.
В программах на языке, поддерживающем несколько свойств с параметрами, нужно выбрать один метод свойств и пометить определяющий его тип атрибутом DefaultMember. Это будет единственное свойство с параметрами, доступное С#-программам.
Обнаружив код, пытающийся получить или заказать значение индексатора, компилятор С# генерирует вызов соответствующего метода-аксессора. Некоторые языки могут не поддерживать свойства с параметрами. Чтобы получить доступ к свойству с параметрами из программы на таком языке, нужно явно вызвать желаемый метод-аксессор. CLR не различает свойства с параметрами и без параметров, поэтому для поиска связи между свойством с параметрами и его методами-аксессорами служит все тот же класс System.Reflection.Propertylnfo.


Словарь терминов
ActiveForm – активные формы, которые представляют собой композицию элементов управления ActiveX. Реализуются в виде динамически связываемых библиотек DLL, но файлы имеют расширение .ocx, а не .dll.
ADO (ActiveX Data Objects – объекты данных ActiveX) – набор компонент и технология, используемая для доступа к БД, поддерживающим спецификацию OLE DB

ADO.NET Entity Framework (EF) объектно-ориентированная технология доступа к данным, является object-relational mapping (ORM) решением для .NET Framework от Microsoft. Предоставляет возможность взаимодействия с объектами как посредством LINQ в виде LINQ to Entities, так и с использованием Entity SQL. Для облегчения построения web-решений используется как ADO.NET Data Services (Astoria), так и связка из Windows Communication Foundation и Windows Presentation Foundation, позволяющая строить многоуровневые приложения, реализуя один из шаблонов проектирования MVC, MVP или MVVM.

ASP – Microsoft® Active Server Pages. ASP представляет собой серверную среду выполнения сценариев, которая может быть использована для создания интерактивных Web-страниц и разработки плновесных Web-приложений. Когда сервер получает ASP-файл, он строит Web-страницу на основе содержащихся в нем сценариев и отправляет ее браузеру. ASP-файлы могут содержать как HTML-код, так и вызовы компонентов COM, которые в свою очередь могут осуществлять связь с БД или выполнять другие операции.
ATL – ActiveX Template Library. Библиотека активных шаблонов ActiveX Microsoft, предназначенная для облегчения процесса создания компактных клиент-серверных приложений, использующих COM и ActiveX. Используется при разработке приложений в Microsoft Visual C++.
Automation controller (синоним – ActiveX client) – контроллер автоматизации в COM-технологиях. Это клиентское приложение по отношению к серверу автоматизации Automation server, называемому также "объектом автоматизации" или "программируемым компонентом". Язык Visual Basic является типичным примером контролера автоматизации, как утверждается в MSDN.
Automation – автоматизация. Основанная на COM технология, которая предоставляет возможность взаимодействия компонентов ActiveX без использования таблиц виртуальных методов (vtable), т.е. вызов методов интерфейсов осуществляется без использования их имен, по "номерам" ID. Прежде называлась OLE Automation.
Automation object – экземпляр класса, который предоставляет свои методы и свойства клиентским приложениям посредством интерфейсов автоматизации (Automation interfaces). См. также dispinterface.
CGI (Common Gateway Interface – общий шлюзовой интерфейс) – интерфейс прикладного программирования, используемый в Internet. CGI - не язык программирования, а спецификация, определяющая взаимодействие внешней программы создания динамических WWW-документов и HTTP-сервера. CGI-программы создаются на многих языках программирования: Си, Perl, Visual Basic и др. Основной недостаток CGI - необходимость обмена данными между клиентом и сервером, что может вызвать большие временные задержки. Первоначально CGI был разработан NCSA
CLX – cross-platform component library (Delphi 6.0)
COM (Component Object Model) – модель компонентных объектов Microsoft. Стандартный механизм, включающий интерфейсы, с помощью которых одни объекты предоставляют свои сервисы другим. Является основой многих объектных технологий, в том числе OLE и ActiveX. Другие переводы: многокомпонентная модель объектов.
Containment – включение (в технологиях на основе COM - механизм многократного использования при наследовании одним объектом методов другого объекта посредством простого вызова, в отличие от агрегации aggregation).
CORBA (Common Object Request Broker Architecture) – технология (спецификация) построения распределенных объектных приложений, предложенная фирмой IBM и разработанная группой компаний OMG. Клиенты взаимодействуют с сервером, используя объекты и интерфейсы. Благодаря тому, что программное ядро CORBA разработано для всех основных аппаратных и программных платформ, эта технология позволяет разрабатывать действительно гетерогенные системы, использующие преимущества каждой платформы.
DCE (Distributed Computing Environment) – среда распределенных вычислений (группа функций независимого от платформ промежуточного обеспечения компании Open Software Foundation для организации совместной работы распределенных программ, в частности, функции обслуживания распределенных файлов, присвоения имен, контроля за временем, обслуживания потоков, дистанционного вызова процедур и обеспечения безопасности). См. также раздел OSF Standards for RPC в MSDN.
DCOM (Distributed Component Object Model) – распределенная модель компонентных объектов. Расширение модели COM фирмы Microsoft, ориентированное на поддержку и интеграцию распределенных объектных приложений, функционирующих в сети.
DDE (Dynamic Data Exchange) – динамический обмен данными. Способ обмена данными между приложениями, который был введен в первых версиях Windows.
Dispinterface – диспетчерский интерфейс (диспинтерфейс). Осуществляет доступ к сервисам COM-объектов в Automation технологиях.
Heterogeneous system – гетерогенная система, т.е. система, построенная из разнородных элементов. Например, это может быть компьютерная сеть, составленная из компьютеров разных типов. Другой пример – запись (record) можно в принципе считать гетерогенным массивом.
H/PC – карманный компьютер (hand-held PC).
IDL (Interface Definition Language) – язык описания интерфейсов (используется в COM-технологиях для спецификации интерфейсов объектов COM).

In-place activation (активизация на месте) – способ редактирования внедренного документа с запуском приложения непосредственно из контейнера (в OLE технологии). См. также антоним – visual editing.

LINQ (Language Integrated Query) проект Microsoft по добавлению синтаксиса языка запросов, напоминающего SQL, в языки программирования платформы .NET Framework. Ранее был реализован в языках C# и Visual Basic .NET. Множество концепций, которые вводит LINQ, изначально опробовали в исследовательском проекте Microsoft C
·.
LINQ выпущен вместе с Visual Studio 2008 в конце ноября 2007 года. Для быстрого создания и отладки запросов LINQ существует специализированная утилита LINQPad.

MAPI – Messaging Application Programming Interface. Интерфейс прикладного программирования [для] электронной почты (корпорации Microsoft).
Marshaling – маршалинг, маршализация. Механизм, который позволяет клиенту получить доступ к объекту, находящемуся в другом процессе или на другом компьютере (невидимо для клиента). В технологии ORPC – процесс упаковки запроса, включая параметры, в стандартный формат, пригодный для передачи по сети.
MIDAS (Multitier Distributed Applications Services) – набор сервисов для многозвенных распределенных приложений. Многозвенное приложение – это распределенная система удаленного доступа к данным, которая состоит минимум из трех логических уровней. Технология MIDAS позволяет обрабатывать данные, расположенные на разных машинах. Она также поддерживает Internet-приложения, взаимодействует с технологиями CORBA, COM и MTS.
MIDL (Microsoft Interface Definition Language) – Microsoft реализация и расширение языка OSF-DCE Interface Definition Language.
MTS (Microsoft Transaction Server). Это среда, которая обеспечивает управление транзакциями, безопасность и объединение ресурсов и объектов в общий фонд (pooling) для распределенных COM приложений. MTS включает в себя целую группу элементов.
MSDN (Microsoft Developer’s Network) – собрание документов компании Microsoft, содержащее сведения обо всех ее разработках.
NCSA (National Computer Security Association) – Национальная ассоциация по компьютерной безопасности (США). Независимая исследовательская организация, проверяющая и сертифицирующая антивирусные программы и Web-узлы. Переименована в ICSA.
.NET Framework программная платформа компании Microsoft, предназначенная для создания обычных программ и веб-приложений. Главной идеей разработки .NET Framework было стремление сделать платформонезависимую виртуальную машину для выполнения одного и того же кода в различных ОС без внесения изменений на момент компиляции. Но со временем Microsoft ограничилась поддержкой только своих операционных систем Windows. Поддержкой некоторых других платформ занимаются независимые разработчики (проекты Mono, Portable.NET).
OCX (OLE Custom eXtension) – специализированные (перемещаемые) элементы управления. Это программируемые компоненты-приложения с интерфейсом на базе OLE, позволяющим легко включать их в другие приложения. С 1996 года они стали называться ActiveX.
ODBC (Open Database Connectivity interface) – открытый интерфейс взаимодействия с базами данных. Представляет собой стандартный API, разработанный Microsoft в 1991г. Позволяет приложениям, работающим под Windows или другими ОС, общаться с различными серверами реляционных баз данных. Этот интерфейс поддерживает запросы на языке SQL и базируется на спецификации Call Level Interface Specification, разработанной консорциумом SQL Access Group. ODBC как реализация – это набор библиотек (.DLL), которые служат промежуточным слоем между приложениями и БД различных типов, обеспечивая независимость приложений от конкретных БД.
OEM – изготовители оригинального оборудования (original equipment manufacturers).
OLE (Object Linking and Embedding) – связывание и внедрение объектов. До 1996 года - общее название группы объектно-ориентированных технологий Microsoft на основе COM (OLE 1, OLE 2, OLE automation, OLE Database и др.). С 1996 года после введения термина ActiveX применяется для обозначения технологий на основе COM, используемых для создания составных документов внедрением и связыванием.
OLE DB – спецификация доступа к данным (прежнее название - Nile), разработанная Microsoft. Объединяет ODBC и OLE.
OMG (Object Management Group) – рабочая группа по развитию стандартов объектно-ориентированного программирования.
OSF (Open Software Foundation) – фонд открытого ПО, консорциум OSF (независимая некоммерческая научно-исследовательская организация, занимающаяся разработкой стандартов для открытых систем). В частности, одним из ее стандартов является язык IDL.

PLINQ (Параллельный LINQ) – является параллельной реализацией LINQ to Objects. PLINQ реализует полный набор стандартных операторов запроса LINQ как методы расширения для пространства имен T:System.Linq и имеет дополнительные операторы для параллельных операций. PLINQ объединяет простоту и удобство чтения синтаксиса LINQ с мощностью параллельного программирования. Подобно коду, предназначенному для библиотеки параллельных задач, запросы PLINQ масштабируют в степень параллелизма на основе возможностей главного компьютера.
Во многих сценариях PLINQ может значительно увеличить скорость запросов LINQ to Objects, более эффективно используя все доступные ядра на главном компьютере. Повышенная производительность увеличивает вычислительную мощностью на рабочем столе.

P/PC – ручной компьютер (palm-size – размером с ладонь). Еще меньше, чем карманный H/PC. Полностью управляется пером и не имеет клавиатуры.

Proxy ("заместитель") – это COM-объект внутри клиентского процесса, предоставляющий клиенту те же интерфейсы, что и объект в локальном сервере, с которым клиент пытается работать. Функцией заместителя является получение вызова от клиентского процесса, его упаковка и отправка в процесс сервера (с помощью механизма RPC). В локальном сервере упакованный вызов передается специализированному объекту – "заглушке" (stub), который распаковывает вызов и передает его требуемому объекту COM. Результат вызова передается клиенту в обратном порядке.
RPC (Remote Procedure Call) – удаленный вызов процедуры. Это средство передачи сообщений, которое позволяет распределенному приложению вызывать сервис различных компьютеров в сети. Применяется в распределенных объектных технологиях, таких как DCOM, CORBA, Java RMI.
Silverlight это программная платформа, включающая в себя модуль для браузера, который позволяет запускать приложения, содержащие анимацию, векторную графику и аудио-видео ролики, что характерно для RIA (Rich Internet application). Версия 2.0 добавила поддержку для языков .NET и интеграцию с IDE.
Silverlight реализована для ОС Windows 2000[3], Windows XP, Windows Server 2003, Windows Vista, Windows 7, Windows 8, Mac OS X 10.4, Mac OS X 10.5, Mac OS X 10.6 и браузеров Internet Explorer, Opera, Mozilla Firefox, Safari, Google Chrome. Silverlight включена в Windows Phone 7, а в будущем также планируется поддержка мобильных устройств, начиная с Windows Mobile 6 и Symbian (Series 60), и, возможно, других платформ.
Stub – "заглушка". См. proxy.
SSL (Secure Sockets Layer) – слой защищенных сокетов. Протокол, гарантирующий безопасную передачу данных по сети; комбинирует криптографическую систему с открытым ключом и блочное шифрование данных.
TAPI – Telephony Application Programming Interface. Интерфейс прикладного программирования (API), используемый программами для передачи данных, факсов и голосовых сообщений (такими программами как НуperTerminal, Dial-up Networking, Phone Dialer и другими коммуникационными приложениями Windows NT).
TLB (Type library, type library file) – библиотека типов. Составной документ OLE, содержащий стандартные описания типов данных, модулей и интерфейсов объектов OLE, используемый другими приложениями для получения информации об OLE-сервере (OLE server). Имеет обычно расширение .TLB и не является текстовым файлом. Просмотреть имеющиеся в системе библиотеки типов можно с помощью программы OLE View из Microsoft Visual Studio Tools. Конкретное описание каждой библиотеки можно получить с помощью этой же программы, выбрав тему меню Object=>View для предварительно выбранной библиотеки. Microsoft Word имеет библиотеку .olb.

TPL (Библиотека параллельных задач) – представляет собой набор открытых типов и API-интерфейсов в пространствах имен System.Threading и System.Threading.Tasks в .NET Framework 4. Цель TPL повышение производительности труда разработчиков за счет упрощения процедуры добавления параллелизма в приложения. TPL динамически масштабирует степень параллелизма для наиболее эффективного использования всех доступных процессоров. Кроме того, в библиотеке параллельных задач осуществляется секционирование работы, планирование потоков в пуле ThreadPool, поддержка отмены, управление состоянием и выполняются другие низкоуровневые задачи. Используя библиотеку параллельных задач, можно повысить производительность кода, сосредоточившись на работе, для которой предназначена программа.
Начиная с .NET Framework 4 библиотека параллельных задач является предпочтительным способом создания многопоточного и параллельного кода. Однако не всякий код подходит для параллелизации; например, если цикл за каждую итерацию выполняет небольшой объем работ или выполняется для небольшого числа итераций, из-за дополнительной нагрузки, которую параллелизация оказывает на систему, код может выполняться медленнее. Кроме того, параллелизация, как и любой многопоточный код, усложняет выполнение программы. Хотя библиотека параллельных задач упрощает многопоточные сценарии, рекомендуется иметь базовое понимание понятий потоков, например блокировки, взаимоблокировки и состояния гонки, чтобы эффективно использовать библиотеку параллельных задач. Дополнительные сведения об основных принципах параллельных вычислений см. в Центре разработчиков компьютеров параллельной обработки данных на сайте MSDN.

Transaction – транзакция. Это группа операторов SQL, которые должны быть все выполнены успешно. Если эти операторы выполнены, то изменения БД фиксируются; в противном случае эти операции отвергаются. Транзакции являются способом сохранения целостности данных при одновременной работе с сервером нескольких клиентов.

Visual editing (визуальное редактирование) – способ редактирования внедренного документа в отдельном окне с последующим сохранением изменений в документе-контейнере (в OLE технологии). См. также антоним in-place activation.

Windows CE – компактная версия Windows, предназначенная в первую очередь для использования изготовителями оригинального оборудования (OEM), а также в карманных (P/PC) и ручных (H/PC – еще меньше) компьютерах. Версия 2.0 выпущена в 1997г. Она уже поддерживает цвет, технологии COM и ActiveX, а также виртуальную машину Java. В отличие от других версий поддерживает множество встраиваемых 32-х разрядных процессоров (а не только Intel и Alpha, как Windows NT).

Widget – элемент управления окном, например бегунок или экранная кнопка. Термин Widget является аббревиатурой от слов window и gadget (приспособление)

WCS (Windows CardSpace) ныне отмененное клиентское ПО с патентованной технологией единого входа от Microsoft. WCS это способ идентификации пользователей при перемещении между ресурсами Интернета без необходимости повторного ввода имен и паролей.
В отличие от ранее используемых технологий унифицированной идентификации (например, Microsoft Passport) WCS управляет непосредственно пользователями и приложениями, с которыми устанавливается контакт (а не из централизованного ресурса). Можно применять разные схемы и уровни сложности для идентификации при доступе на Web-форумы и для банковских операций.
15 февраля 2011 корпорация Майкрософт объявила об отмене Windows CardSpace 2.0 и о работе над замещающим ПО U-Prove.

WCF (Windows Communication Foundation) программный фреймворк, используемый для обмена данными между приложениями, входящий в состав .NET Framework. До своего выпуска в декабре 2006 года в составе .NET Framework 3.0, WCF был известен под кодовым именем Indigo.
WCF делает возможным построение безопасных и надёжных транзакционных систем через упрощённую унифицированную программную модель межплатформенного взаимодействия. Комбинируя функциональность существующих технологий .NET по разработке распределённых приложений (ASP.NET XML Web Services ASMX, WSE 3.0, .NET Remoting, .NET Enterprise Services и System.Messaging), WCF предоставляет единую инфраструктуру разработки, при умелом применении повышающую производительность и снижающую затраты на создание безопасных, надёжных и транзакционных Web-служб нового поколения. Заложенные в неё принципы интероперабельности позволяют организовать работу с другими платформами, для чего используются технологии взаимодействия платформ, например WSIT, разрабатываемые на базе открытого исходного кода.

WF (Windows Workflow Foundation) – представляет собой технологию компании Microsoft для определения, выполнения и управления рабочими процессами (англ. workflow). Данная технология входит в состав .NET Framework 3.0, который изначально установлен в Windows Vista и может быть установлен в Windows 2003 Server и Windows XP SP2. WF ориентирована на визуальное программирование и использует декларативную модель программирования.
При помощи WF могут быть описаны три типа процессов:
последовательный процесс (Sequential Workflow) переход от одного шага в другой без возвратов обратно;
конечный автомат (State-Machine Workflow) переход из одного состояния в другое, возможны и произвольные возвраты в предыдущие состояния;
процесс, управляемый правилами (Rules-driven Workflow) частный случай последовательного процесса, в котором переход на следующий шаг определяется набором правил.

WPF (Windows Presentation Foundation) это система следующего поколения (за .NET FrameWork – Windows Forms) для построения клиентских приложений Windows с визуально привлекательными возможностями взаимодействия с пользователем. С помощью WPF можно создавать широкий спектр как автономных, так и размещенных в браузере приложений. В основе WPF лежит векторная система визуализации, не зависящая от разрешения и созданная с расчетом на возможности современного графического оборудования. WPF расширяет базовую систему полным набором функций разработки приложений, в том числе языком XAML (Extensible Application Markup Language), элементами управления, привязкой данных, макетом, двухмерной- и трехмерной-графикой, анимацией, стилями, шаблонами, документами, мультимедиа, текстом и оформлением. WPF входит в состав Microsoft .NET Framework (начиная с версии 3.0) и позволяет создавать приложения, включающие другие элементы библиотеки классов .NET Framework. ([ Cкачайте файл, чтобы посмотреть ссылку ] )


Список литературы по языку С++ и программированию для Windows
1. Круглински Д., Уингоу С., Шеферд Дж. Программирование на Microsoft Visual C++ для профессионалов/Пер. с англ. – СПб:Питер; М.: Издательско-торговый дом "Русская редакция", 2000. - 864с.
(Есть в электронном виде – файл
Круглински_Уингоу_Шеферд_Программирование_на_MV_C++_6.pdf)
Эта книга – настоящая библия программирования на Microsoft Visual C++6.0 с применением библиотеки классов MFC. Ориентирована на профессиональных программистов, владеющих языком С, имеющим представление о С++ и приступающих к разработке 32-х разрядных приложений для Windows 95/98/NT 4.0.
В книге рассмотрена разработка разноообразных приложений (обработка событий, управление памятью, печать, GDI, DIB, SDI/MDI, ActiveX, COM, ATL, последние расширения COM, поддержка БД, программирование для Интернета с использованием Windows Sockets, MFC WinInet и ISAPI).

2. Дейтел Х.М., Дейтел П.Дж. Как программировать на С++/Пер. с англ. – Бином, 2000. – 1024с.
(Есть в электронном виде – файл Дейтел_Х_Дейтел_П_Как_программировать_на_С++.pdf)
Это учебник по языку С++, рассчитанный как на начинающих программистов, так и на опытных. При изложении материала авторы стараются абстрагироваться от аппаратной и программной платформы и компиляторов, хотя примеры написаны для компилятора Borland C++. Эта работа представляет собой очень хороший учебник по языку С++, хотя и несколько устаревший в том смысле, что в нем не рассматриваются, например, контейнеры и библиотека стандартных шаблонов STL, которые имеются в нынешнем стандарте языка С++ от 1999г.

3. Страуструп Б. Язык программирования С++, 3-е изд. / Пер. с англ. – СПб.: М.: "Невский Диалект" – "Издательство БИНОМ", 1999. – 991.
Излагается стандарт языка С++ (ISO/IEC 14882 "Standard for the C++ Programming Language")
Как и другие книги этого автора, они довольно тяжелы для изучения, но содержат очень квалифицированное и полное изложение особенностей языка, не связываемое с какой-либо программной или аппаратной платформой.

4. Элджер Д. Библиотека программиста С++ ~1998г.
(Есть в электронном виде – файл Элджер__С++_Библиотека_программиста.pdf)

5. Степанов А., Менг Ли. Руководство по стандартной библиотеке шаблонов (STL)/ Пер. на русский А.Суханова и А.Кутырина – М.: 1999.
(Есть в электронном виде – файл Степанов_Ли__Руководство_по_STL.chm)
Степанов считается практически единственным разработчиком библиотеки STL и эта книга представляет собой действительно хорошее руководство по STL.

6. Либерти Дж. С++. Энциклопедия пользователя.
(Есть в электронном виде – файл Либерти__С++_Энциклопедия_пользователя.pdf)
Эта книга действительно является энциклопедией в том смысле, что охватывает очень многие стороны языка С++: ООП, UML, библиотека STL и алгоритмы, MFC, живучесть объектов, хеширование, шифрование, CORBA, COM и др.

7. Подбельский В.В. Язык Си++.Учебное пособие.- 2-е изд.,перераб. и доп.-М.:Финансы и статистика,1996.-560с.:ил.
Вышло уже и 4-е издание в 1999г. (просто 4-е издание, не переработанное и не дополненное). ISSN 5-279_01670_5.
Подробно рассмотрены синтаксис, семантика и техника программирования на Си++, который в данной книге рассматривается как самостоятельный язык программирования а не расширение Си. Книга хороша в методическом плане, содержит много примеров программ. Недостаток книги в том, что рассматриваемая в ней версия языка несколько устарела.

8. В.Н.Овсянник «Язык С++ не для чайников»
Имеется только в электронном формате. В основном материал относится к версии языка С++, реализованной в компиляторе ВС3.1 (последняя версия от Borland для MS DOS).

8а. Джеффри Рихтер, Кристоф Назар. Windows via C/C++. Программирование на языке Visual C++. Изд-во: Питер, Русская Редакция, 2009 г., 896 стр.
ISBN 978-5-388-00205-1, 978-5-7502-0367-3, 978-0-7356-2424-5

Файл Рихтер_Назар_Windows via C++.pdf
в папке windows_via_c_c_programmirovanie_na_jazyke_visual_c. В этой же папке есть и много примеров программ.

Это издание практически новая книга, посвященная разработке серьезных приложений на Visual C++ в операционных системах Windows XP и Windows Vista (32- и 64-разрядных версиях) с использованием функций Windows API. Гораздо глубже, чем в предыдущих изданиях, рассматриваются такие темы, как механизм User Account Control, взаимодействие с системой библиотеки C/C++ при реализации защитных механизмов и обработке исключений; представлены новые синхронизирующие механизмы. В это издание добавлены две совершенно новые главы: о механизмах ввода-вывода и о работе новой системы Windows Error Reporting, изменившей подходы к созданию отчетов об ошибках и восстановлению приложений после сбоев. Книга предназначена для профессиональных программистов, владеющих языком С/С++ и имеющим опыт разработки Windows-приложений. Исходные тексты для всех программ-примеров из книги читатели найдут на веб-сайте поддержки нового издания. Книга состоит из 26 глав и двух приложений.

Список литературы по платформе .NET
9. Дон Бокс, Крис Селлз. Основы платформы .NET, том 1. Общеязыковая исполняющая среда /Пер. с англ. – М.; СПб.: К.: Издательский дом "Вильямс", 2003. – с.288. ISBN 5-8459-0455-2

13 февраля 2002 года закончилась эпоха COM и началась эпоха CLR. В этот день в составе пакета .NET Framework увидела свет виртуализованная среда исполнения CLR. В книгах не столь высокого теоретического уровня, как эта сложно осветить многие возможности, предоставляемые виртуализованной средой исполнения. Пожертвовав популярностью, автор вводит читателя за кулисы высокоуровневых средств программирования, не углубляясь при этом в дебри низкоуровневых кодов. Книга предназначена для профессиональных программистов и не может служить единственным учебником, так как в ней отсутствуют объяснения многих общеизвестных положений, которые можно найти в другой литературе. Тем не менее она все же является учебником, благодаря последовательности изложения и практической направленности рассматриваемых вопросов.

10. Деймьен Уоткинз, Марк Хаммонд, Брэд Эйбрамз. Программирование на платформе .NET среда /Пер. с англ. – М.; СПб.: К.: Издательский дом "Вильямс", 2003. – с.368. ISBN 5-8459-0456-0

Книга написана группой разработчиков платформы .NET и позволяет узнать, как на самом деле устроена эта платформа. В отличие от многих других книг на эту тему, она описывает не только "как" работают элементы платформы .NET, но и "почему". Несомненным достоинством книги является глубокий анализ платформы .NET с объяснением причин, по которым разработчики среди множества альтернативных вариантов выбрали именно те, которые легли в основу платформы .NET. Первая часть книги начинается сравнительным анализом прежних распределенных систем и платформы .NET (глава 1), после чего предлагается подробное обсуждение системы типов (глава 2) и метаданных (глава 3). При описании системы выполнения (глава 4) приведены тонкости использования промежуточного языка, рассматриваются системы безопасности и управления политиками. Большое внимание в книге уделяется процессу создания (глава 5) и развертывания (глава 6) приложений с углубленным описанием вопросов контроля версий, интернационализации и локализации, которые лишь кратко упоминаются в других книгах о платформе .NET.
Обзор библиотеки классов Framework Class Library (глава 7) насыщен простыми и ясными примерами ее использования. Вторая часть состоит из восьми приложений, которые посвящены реализациям совершенно разных языков программирования (от Visual Basic и Perl до Pascal и Mondrian) для платформы .NET. Книга рассчитана на широкий круг читателей с разной подготовкой: от студентов, желающих на простых примерах познакомиться с новой технологией программирования, до профессионалов высокого уровня, интересующихся тонкостями реализации новой платформы и переноса на нее унаследованных решений.

11. Джеффри Рихтер. CLR via C#. Программирование на платформе Microsoft .NET Framework 2.0 на языке C#. Мастер-класс / Пер. с англ. – М.:Издательство «Русская редакция»; СПб.: Питер, 2007. - 656с.
Есть в электронном формате (djvu).



Известный хороший автор и хорошая книга.
12. Джеффри Рихтер. Программирование на платформе Microsoft .NET Framework / Пер. с англ. – М.:Издательско-торговый дом «Русская редакция», 2003 - 512с.
Есть в электронном формате (.pdf)


13. [ Cкачайте файл, чтобы посмотреть ссылку ] - очень неплохой ресурс, содержащий много полезного по .NET и C# в частности. Много материала по WPF.

14. [ Cкачайте файл, чтобы посмотреть ссылку ] - Примеры приложений (WPF) NET Framework 3.5

15. ..\C_Sharp\DOC\SampleViewerLite – Проект, который показывает содержимое .xaml для управляющих элементов WPF

16. ms-help://MS.MSDNQTR.v90.en/dv_fxsamples/html/a3726213-ab77-4b45-800a-93fafd52d0b9.htm - Примеры (samples) по .NET в MSDN

































































13PAGE 15






















13PAGE 15


13PAGE 14215
C#Lect.doc

















































































Заголовок 1 Заголовок 2 Заголовок 3 Заголовок 4 Заголовок 5 Заголовок 6 Заголовок 715

Приложенные файлы

  • doc 24082603
    Размер файла: 2 MB Загрузок: 0

Добавить комментарий