Принцип действия CLR
Среда CLR управляет выполнением кода .NET. Действует она по следующему принципу. Результатом компиляции программы на C# является не исполняемый код, а файл, содержащий особого рода псевдокод, называемый Microsoft Intermediate Language, MSIL (промежуточный язык Microsoft). Псевдокод MSIL определяет набор переносимых инструкций, не зависящих от конкретного процессора. По существу, MSIL определяет переносимый язык ассемблера. Следует, однако, иметь в виду, что, несмотря на кажущееся сходство псевдокода MSIL с байт-кодом Java, это все же разные понятия.
Назначение CLR — преобразовать промежуточный код в исполняемый код по ходу выполнения программы. Следовательно, всякая программа, скомпилированная в псевдокод MSIL, может быть выполнена в любой среде, где имеется реализация CLR. Именно таким образом отчасти достигается переносимость в среде .NET Framework.
Псевдокод MSIL преобразуется в исполняемый код с помощью ]1Т-компилятора. Сокращение JIT означает точно в срок и отражает оперативный характер данного компилятора. Процесс преобразования кода происходит следующим образом. При выполнении программы среда CLR активизирует JIT-компилятор, который преобразует псевдокод MSIL в собственный код системы по требованию для каждой части программы. Таким образом, программа на C# фактически выполняется как собственный код, несмотря на то, что первоначально она скомпилирована в псевдокод MSIL. Это означает, что такая программа выполняется так же быстро, как и в том случае, когда она исходно скомпилирована в собственный код, но в то же время она приобретает все преимущества переносимости псевдокода MSIL.
Помимо псевдокода MSIL, при компилировании программы на C# получаются также метаданные, которые служат для описания данных, используемых в программе, а также обеспечивают простое взаимодействие одного кода с другим. Метаданные содержатся в том же файле, что и псевдокод MSIL.
Управляемый и неуправляемый код
Как правило, при написании программы на C# формируется так называемый управляемый код. Как пояснялось выше, такой код выполняется под управлением среды CLR, и поэтому на него накладываются определенные ограничения, хотя это и дает ряд преимуществ. Ограничения накладываются и удовлетворятся довольно просто: ком пи-лятор должен сформировать файл MSIL, предназначенный для выполнения в среде CLR, используя при этом библиотеку классов .NET, — и то и другое обеспечивается средствами С#. Ко многим преимуществам управляемого кода относятся, в частности, современные способы управления памятью, возможность программирования на разных языках, повышение безопасности, поддержка управления версиями и четкая организация взаимодействия программных компонентов.
В отличие от управляемого кода, неуправляемый код не выполняется в среде CLR. Следовательно, до появления среды .NET Framework во всех программах для Windows применялся неуправляемый код. Впрочем, управляемый и неуправляемый коды могут взаимодействовать друг с другом, а значит, формирование управляемого кода в C# совсем не означает, что на его возможность* взаимодействия с уже существующими программами накладываются какие-то ограничения.
Общеязыковая спецификация
Несмотря на все преимущества, которые среда CLR дает управляемому коду, для максимального удобства его использования вместе с программами, написанными на других языках, он должен подчинятся общеязыковой спецификации (Common Language Specification — CLS), которая определяет ряд общих свойств для разных .NET-совместимых языков. Соответствие CLS особенно важно при создании программных компонентов, предназначенных для применения в других языках. В CLS в качестве подмножества входит общая система типов (Common Type System — CTS), в которой определяются правила, касающиеся типов данных. И разумеется, в C# поддерживается как CLS, так и CTS.
ГЛАВА 2 Краткий обзор элементов C#
Наибольшие трудности в изучении языка программирования вызывает то обстоятельство, что ни один из его элементов не существует обособленно. Напротив, все элементы языка действуют совместно. Такая взаимосвязанность затрудняет рассмотрение одного аспекта C# безотносительно к другому. Поэтому для преодоления данного затруднения в этой главе дается краткий обзор нескольких средств языка С#, включая общую форму программы на С#, ряд основных управляющих и прочих операторов. Вместо того чтобы углубляться в детали, в этой главе основное внимание уделяется лишь самым общим принципам написания любой программы на С#. А большинство вопросов, затрагиваемых по ходу изложения материала этой главы, более подробно рассматриваются в остальных главах части I.
Объектно-ориентированное программирование
Основным понятием C# является объектно-ориентированное программирование (ООП). Методика ООП неотделима от С#, и поэтому все программы на C# являются объектно-ориентированными хотя бы в самой малой степени. В связи с этим очень важно и полезно усвоить основополагающие принципы ООП, прежде чем приступать к написанию самой простой программы на С#.
ООП представляет собой эффективный подход к программированию. Методики программирования претерпели существенные изменения с момента изобретения
компьютера, постепенно приспосабливаясь, главным образом, к повышению сложности программ. Когда, например, появились первые ЭВМ, программирование заключалось в ручном переключении на разные двоичные машинные команды с переднего пульта управления ЭВМ. Такой подход был вполне оправданным, поскольку программы состояли всего из нескольких сотен команд. Дальнейшее усложнение программ привело к разработке языка ассемблера, который давал программистам возможность работать с более сложными программами, используя символическое представление отдельных машинных команд. Постоянное усложнение программ вызвало потребность в разработке и внедрении в практику программирования таких языков высокого уровня, как, например, FORTRAN и COBOL, которые предоставляли программистам больше средств для того, чтобы как-то справиться с постоянно растущей сложностью программ. Но как только возможности этих первых языков программирования были полностью исчерпаны, появились разработки языков структурного программирования, в том числе и С.
На каждом этапе развития программирования появлялись методы и инструментальные средства для "обуздания" растущей сложности программ. И на каждом таком этапе новый подход вбирал в себя все самое лучшее из предыдущих, знаменуя собой прогресс в программировании. Это же можно сказать и об ООП. До ООП многие проекты достигали (а иногда и превышали) предел, за которым структурный подход к программированию оказывался уже неработоспособным. Поэтому для преодоления трудностей, связанных с усложнением программ, и возникла потребность в ООП.
ООП вобрало в себя все самые лучшие идеи структурного программирования, объединив их с рядом новых понятий. В итоге появился новый и лучший способ организации программ. В самом общем виде программа может быть организована одним из двух способов: вокруг кода (т.е. того, что фактически происходит) или же вокруг данных (т.е. того, что подвергается воздействию). Программы, созданные только методами структурного программирования, как правило, организованы вокруг кода. Такой подход можно рассматривать "как код, воздействующий на данные".
Совсем иначе работают объектно-ориентированные программы. Они организованы вокруг данных, исходя из главного принципа: "данные управляют доступом к коду". В объектно-ориентированном языке программирования определяются данные и код, которому разрешается воздействовать на эти данные. Следовательно, тип данных точно определяет операции, которые могут быть выполнены над данными.
Для поддержки принципов ООП все объектно-ориентированные языки программирования, в том числе и С#, должны обладать тремя общими свойствами: инкапсуляцией, полиморфизмом и наследованием. Рассмотрим каждое из этих свойств в отдельности.
Инкапсуляция
Инкапсуляция — это механизм программирования, объединяющий вместе код и данные, которыми он манипулирует, исключая как вмешательство извне, так и неправильное использование данных. В объектно-ориентированном языке данные и код могут быть объединены в совершенно автономный черный ящик. Внутри такого ящика находятся все необходимые данные и код. Когда код и данные связываются вместе подобным образом, создается объект. Иными словами, объект — это элемент, поддерживающий инкапсуляцию.
В объекте код, данные или же и то и другое могут быть закрытыми или же открытыми. Закрытые данные или код известны и доступны только остальной части объекта. Это означает, что закрытые данные или код недоступны части программы, находящейся за пределами объекта. Если же данные или код оказываются открытыми, то они доступны другим частям программы, хотя и определены внутри объекта. Как правило, открытые части объекта служат для организации управляемого интерфейса с закрытыми частями.