|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Пpиложение BC# для разработчиков Java В "Искусстве войны" Сунь Цзы утверждает, что "необходимо рассматривать вещи большой важности с небольшим усилием, а вещи небольшой важности с большим усилием". Это может звучать странно, но автор хочет, видимо, сказать, что если заботиться о незначительных вещах, то важные вещи тогда позаботятся о себе сами. Как это применимо к C# и Java? При первом взгляде на код C# может показаться, что он не слишком впечатляющий, так как обнаруживается отчетливое сходство между ним и Java. Однако, если вы ожидали каких-то существенных изменений, то правда состоит в том, что на самом деле существует не так уж много синтаксических различий. Пути двух языков расходятся во внутренних тонкостях таких вещей, как перегрузка операторов, индексаторы, делегаты, свойства и перечисления с контролем типов. При более внимательном рассмотрении можно понять, что в конце концов между ними существует большое различие. (Приведенные выше темы будут подробнее рассмотрены позже в этом приложении.) В этом приложении мы сосредоточимся прежде всего на вопросе, важном для разработчиков Java: как можно использовать в C# опыт использования Java, а также подчеркнем свойства присущие C#, и рассмотрим, что C# делать не может. Здесь предполагается, что читатель хорошо знаком с Java, поэтому отсутствует подробное описание языка Java, за исключением некоторых существующих различий. ОсновыОдно из основных различий между C# и Java лежит не в самом языке, а в платформе, поверх которой они реализованы. Программам Java требуется для выполнения кода рабочая среда времени выполнения Java Runtime Environment. C# и, на самом деле, вся платформа .NET выполняются в среде Common Language Runtime. Большинство свойств CLR, внутреннее управление памятью, согласованность среды, масштабируемость и независимость от базовой платформы отражены в JRE Java. В то время, как JRE ограничена исключительно одним языком Java, CLR предоставляет поддержку и интеграцию нескольких языков с помощью VOS (virtual object system — система виртуальных объектов), которая предоставляет богатую типами систему, предназначенную для реализации множества различных типов языков программирования. Исходный код Java можно компилировать в промежуточное состояние, называемое байт-кодом. Он может затем выполняться с помощью поставляемой виртуальной машины. CLR, наоборот, не предоставляет виртуальную машину. Код C# также компилируется в промежуточное состояние, называемое для удобства промежуточным языком (IL, Intermediate Language). Но код IL передается в управляемые CLR процессы выполнения или компиляторам JIT CLR, обычно называемым JITters, которые преобразуют по требованию разделы IL в собственный код. Давайте рассмотрим известный пример "Hello, World!" на Java (который будет показан здесь без затенения): public class Hello { public static void main(String args[]) { System.out.println("Hello world! This is Java Code!"); } } Соответствующий код C# для этого примера следующий (представлен на сером фоне): public class Hello { public static void Main(string[] args) { System.Console.WriteLine("Hello world! This is C# code!"); } } Прежде всего можно заметить, что эти два кода очень похожи, по большей части различия незначительны (такие, как использование заглавных букв в stringи Mainи использование System.Console.WriteLineвместо System.out.println). Он является, с другой стороны, по-прежнему зависимым от регистра символов. Необходимо отметить тип string, который в C# также может записываться с заглавной S — как String. Следовательно, приведенный выше код можно записать в следующем виде: public class Hello { public static void Main(String [] args) { System.Console.WriteLine("Hello world! This is C# code!"); } } Можно заметить, что спецификатор ранга массива []перемещен из позиции перед переменной argsв примере Java в положение между типом stringи переменной argsв примере C#. В C# спецификатор ранга массива должен появляться перед именем переменной, так как массив является на самом деле типом данных, что указывается с помощью []: // C# int [] X; // массив целых чисел в C# // пример java int [] х; // массив целых чисел в java int x[]; // массив целых чисел в java Тот же самый код C# можно также представить следующим образом: using System; class Hello { public static int Main() { Console.WriteLine ("Hello world! This is C# code!"); return 0; } } Как можно видеть, изменилось несколько элементов. Объявление string [] argsв сигнатуре метода является в C# необязательным (хотя скобки должны стоять перед именем параметра), как и использование public при объявлении метода. Ключевое слово usingаналогично по смыслу ключевому слову includeв Java, и так как Systemявляется используемым по умолчанию включением в C#, первая строка позволяет нам опустить System, которое находилось перед Console.WriteLine. Поскольку мы используем в этом примере intвместо void, необходимо включить строку return 0;. Блоки кода C# заключаются в фигурные кавычки, также как в Java. Можно сказать, что метод Main()является частью класса, так как он заключен в фигурные кавычки. Точкой входа в приложении C# является метод static Main, как требует компилятор. Необходимо отметить, что Java использует main()в нижнем регистре. Подчеркнем также, что только один класс в приложении может иметь Main. Модификатор доступа public(обсуждаемый позднее) объявляет метод доступным потребителям кода вне класса, пакета или приложения, и также, как и компилятор, требует сделать метод видимым. Аналогично в Java ключевое слове staticпозволяет вызывать метод сначала без создания экземпляра класса. Для метода Main()можно выбрать в качестве возвращаемого типа значения voidили int. voidопределяет, что метод не возвращает значение, a intопределяет, что он возвращает целое значение. ИдентификаторыКлючевые слова, рассматриваемые в следующем разделе, не могут служить идентификаторами ни в Java, ни в C#, однако в C# можно использовать ключевые слова как идентификаторы, помещая перед ними символ @. Отметим, что это исключение имеет отношение только к ключевым словам и не нарушает другие правила. Оба языка являются зависимы ми от регистра символов, поэтому идентификаторы должны иметь согласованное использование заглавных букв. Хотя идентификаторы могут содержать буквы и цифры, первый символ идентификатора как в C#, так и в Java не должен быть цифрой. Java не допускает никаких символов кроме $, а C# вообще не допускает никаких символов: int 7х; // неверно, цифра не может начинать идентификатор int х7; // верно, цифра может быть частью идентификатора int х; // верно int х$; // неверно, никакие символы недопустимы int @7k; // неверно, @ работает только для ключевых слов int @class; // верно, @ перед ключевым словом позволяет использовать // его в качестве идентификатора Стандарты именованияОдним из основных различий, которое может быть не очевидно на первый взгляд, и которое не связано специально с языком C#, является синтаксис записи идентификаторов. Java практикует обозначения типа camel, означающее, что методы и идентификаторы используют меленькую букву для первой буквы имени и заглавную букву для первой буквы любого другого слова в имени. Общий синтаксис, которому следуют большинство программистов в Java, представлен ниже: int id; int idName; int id_name; // также используется final int CONSTANT_NAME; // широко распространен int reallyLongId; public class ClassName; // каждая первая буква заглавная public interface _InterfaceName; // с предшествующим подчеркиванием public void method(){} public void methodName(){} public void longMethodName(){} public void reallyLongMethodName(){} На основе библиотеки классов, предоставленной компанией Microsoft для C#, можно сделать некоторые предположения о стандартах наименований в C#. Документированные рекомендации по именованию для C# не были представлены в то время когда писалась эта книга. Каждая первая буква идентифицирующих имен всех методов и свойств будет заглавной, так же как и каждая первая буква имен всех классов и пространств имен (рассматриваемых позже). Интерфейсы используют в качестве первого символа I. Некоторые примеры приведены ниже: int id; int idName; public class ClassName // каждая первая буква заглавная public interface IInterfaceName // имени интерфейса предшествует I public void Method(){} // первая буква всегда заглавная public void MethodName(){} // первая буква всех других слов // будет заглавная public void LongMethodName(){} public void ReallуLongMetodName(){} Ключевые словаКак известно, ключевое слово является специальным зарезервированным словом языка. Мы уже встречали некоторые из них допустим, объявление переменной как целого числа с помощью int. Другими примерами ключевых слов являются public, class, staticи voidв листингах кода в этом приложении. Ключевые слова можно разделить на ряд категорий в связи с их назначением. В этом разделе мы выделим и определим каждую категорию, а также идентифицируем ключевые слова. Реальные ключевые слова будут идентифицироваться своими версиями в Java, чтобы можно было легко их находить. Затем будет дан эквивалент C# (если существует). Для тех ключевых слов, которые присутствуют только в Java, будет предоставлено лучшее соответствие. Ключевые слова, представленные в C#, но не в Java, будут даны в своей собственной категории с лучшим приблизительным эквивалентом в Java (если такой существует). Простейшие ключевые слова: byte, char, short, int, long, float, double и booleanПримитивные типы данных в обоих языках ссылаются на низкоуровневые типы значений языка. Конечно, диапазон значений указанных типов может различаться в том или другом языке. Логические значения в C# идентифицируются ключевым словом bool в противоположность boolean в Java. Ниже представлен табличный список типов данных Java и их аналогов в C#:
Существует также ряд типов, поддерживаемых C#, которые Java не использует. Таблица ниже выделяет эти типы данных.
Эти ключевые слова сами являются переменными. Оба языка, Java и C#, имеют по три ключевых слова, которые попадают в эту категорию. Ключевые слова thisи voidобладают в обоих языках одинаковой функциональностью. super— эта ссылочная переменная используется для указания класса-предка. В C# эквивалентом является base. Возьмем класс Power, который предоставляет возможность найти степень заданного числа и степень, в которую требуется возвести (при условии, что не происходит переполнение): public class SuperEX { int power; public SuperEX(int power) { this.power = power; } public int aMethod(int x) { int total = 1; for (int i = 0; i < power; i++) { total *= x; } return total; } public static void main(String args[]) { SuperEX x = new SuperEX(Integer.parseInt(args[0])); int tot = x.aMethod(Integer.parseInt(args[1])); System.out.println(tot); } } Класс-потомок этого класса сможет получить доступ к методу aMethodс помощью вызова super.aMethod(<int value>), к переменной power— с помощью вызова super.power = <int value>, и даже к конструктору — с помощью вызова super(<int value>), где <int value>может быть любым целым литералом, переменной или константой. Аналогично в C# класс-потомок этого класса сможет получить доступ к методу aMethodс помощью вызова super.aMethod(<int value>)и к переменной power— с помощью вызова super.power = <int value>. Сделать вызов базового конструктора тоже возможно, синтаксис, однако, будет отличаться. Пример ниже является эквивалентом в C# для SuperEX: namespace SuperEX { using System; public class SuperEX { internal int power; public SuperEX(int power) { this.power = power; } public int aMethod(int x) { int total = 1; for (int i = 0; i < power; i++) { total *= x; } return total; } public static void Main(String [] args) { SuperEX x = new SuperEX(int.Parse(args[0])); int tot = x.aMethod(int.Parse(args[1])); Console.WriteLine(tot); } } public class Child: SuperEX { public Child() : base(55) { } } } Как можно видеть на примере класса-потомка Child, вызов конструктора базового класса является частью объявления конструктора класса-потомка. Программист может по своему усмотрению определить список параметров конструктора класса-потомка, но ссылка на конструктор базового класса должна соответствовать списку аргументов, требуемых базовым классом. В данном примере конструктор потомка может получить форму <child constructor>: base constructor(<int value>), где <int value>может быть любым целым литералом, переменной или константой, a <child constructor>представляет любой конструктор потомка, который хочет воспользоваться конструктором базового класса. Более общая версия, как получить доступ к конструктору базового класса, представлена ниже: ChildConstructor(argument_list) : BaseConstructor(argument_list)Ключевые слова управления пакетами: import и package Так же как в Java, в C# инструкции importпредоставляют доступ к пакетам и классам в коде без полной квалификации, директива usingможет использоваться для того, чтобы сделать компоненты пространства имен видимыми в классе без полной квалификации. В C# не существует эквивалента инструкции package. Чтобы сделать класс частью пространства имен, надо поместить его в объявление пространства имен. Пространства имен будут обсуждаться более подробно позже в этой главе. Ключевые слова управления потоком выполнения и итерациями: break, case, continue, default, do, else, for, if, instanceof, return, switch и while Большинство упомянутых выше ключевых слов имеют одинаковые имена, синтаксис и функциональность в C# и Java. Исключением является оператор Java instanceof, используемый для определения того, что объект является экземпляром класса или некоторого подкласса этого класса. C# предоставляет такую же функциональность с помощью ключевого слова is. Некоторые примеры того, как инструкции работают в C#, даны ниже. Можно видеть, что большая часть кода точно такая же, как и в Java: public static void Main (string[] args) int option = int.Parse(arg[0]); if (option == 1) { // что-нибудь сделать } else if (option == 2) { // сделать что-нибудь еще } switch (option) { case 1: // сделать что-нибудь break; case 2: // сделать что-нибудь еще default: break; } } C# вводит инструкцию foreach, используемую специально для перебора без изменения элементов коллекции или массива, чтобы получить желаемую информацию. Изменение содержимого может иметь непредсказуемые побочные эффекты. Инструкция foreachобычно имеет форму, показанную ниже: foreach(ItemType item in TargetCollection) ItemTypeпредставляет тип данных, хранящихся в коллекции или массиве, a TargetCollectionпредставляет реальный массив или коллекцию. Существует два набора требований, которым должна удовлетворять коллекция, перебор элементов которой будет выполняться с помощью инструкции foreach. Первый набор имеет отношение к составу самой коллекции. Это следующие требования: □ Тип коллекции должен быть интерфейсом, классом или структурой. □ Тип коллекции должен включать метод GetEnumerator()для возврата типа перечислителя. Тип перечислителя является по сути объектом, который позволяет перебрать коллекцию элемент за элементом. Второй набор требований имеет дело с составом типа перечислителя, возвращаемого упомянутым выше методом GetEnumerator(). Список требований дан ниже: □ Перечислитель должен предоставить метод MoveNext()типа boolean. □ MoveNext()должен возвращать true, если в коллекции есть еще элементы. □ MoveNext()должен увеличивать счетчик элементов при каждом вызове. □ Тип перечислителя должен предоставлять свойство с именем Current, которое возвращает ItemType(или тип, который может быть преобразован в ItemType). □ Метод доступа свойства должен возвращать текущий элемент коллекции. Следующий пример использует foreachдля просмотра таблицы Hashtable: Hashtable t = new Hashtable(); t["a"] = "hello"; t["b"] = "world"; t["c"] = "of"; t["d"] = "c-sharp"; foreach (DictionaryEntry b in t) { Console.WriteLine(b.Value); }Ключевые слова модификации доступа: private, protected, public, и (пакет по умолчанию) Ключевое слово privateиспользуется, чтобы сделать методы и переменные доступными только изнутри элементов содержащего их класса. Функциональность одинакова в обоих языках, модификатор publicпозволяет сущностям вне пакета получить доступ к внутренним элементам. Конечно, для C# это будут сущности вне пространства имен, а не пакета. C# и Java различаются в том, как обрабатываются protectedи 'default'. В то время как в Java protectedделает метод или переменную доступной для классов в том же пакете или подклассах класса, protectedв C# делает код видимым только для этого класса и подклассов, которые от него наследуют. C# вводит также новый модификатор доступа — internal. Ключевое слово internalизменяет члены данных так, что они будут видимы всему коду внутри компонента, но не клиентам этого компонента. Различие здесь между модификатором в Java, который указывает элемент, доступный только для элементов в пакете, и internalсостоит в том, что internalдоступен всем элементам сборки, которая может охватывать несколько пространств имен. Сборки и пространства имен будут рассмотрены позже в этом приложении. Модификаторы: abstract, class, extends, final, implements, interface, native, new static, synchronized, transient, volatile Модификатор abstractимеет одну и ту же форму и синтаксис в обоих языках. Таким же является ключевое слово class. C# не имеет модификаторов extendsили implements. Чтобы вывести из класса или реализовать интерфейс, используйте оператор :. Когда список базового класса содержит базовый класс и интерфейсы, базовый класс следует первым в списке. Ключевое слово interfaceиспользуется для объявления интерфейса. Примеры рассмотренных ранее концепций приведены ниже: class ClassA: BaseClass, Iface1, Iface2 { // члены класса } public interface IfruitHaver { public void Fruit(); } public class plant: IfruitHaver { public Plant() { } public void Fruit() { } } class Tree : Plant { public Tree() { } } Ключевое слово finalв Java трудно отнести к какой-то категории. Частично причина заключается в том, что оно предоставляет вид функциональности два-в-одном, что делает трудным соединение его с каким-либо одним назначением. Объявление класса как finalзапечатывает его, делая невозможным расширение. Объявление метода как finalтакже его запечатывает, делая невозможным его переопределение. Объявление переменной как finalявляется по сути объявлением ее только для чтения. Именно для чтения, а не константой, так как возможно задать значение finalкак значение переменной. Значения констант должны быть известны во время компиляции, поэтому константы могут задаваться равными только другим константам. В противоположность этому C# предоставляет специфические приемы для рассмотрения каждого отдельного вопроса. По умолчанию подкласс не должен иметь возможности повторно реализовывать открытый метод, который уже реализован в суперклассе. C# вводит новую концепцию — сокрытие метода, что позволяет программисту переопределить члены суперкласса в классе-наследнике и скрыть реализацию базового класса, C# использует в данном случае модификатор new. Это делается присоединением newк объявлению метода. Достоинство сокрытия версий членов базового класса состоит в том, что можно выборочно определить, какую реализацию использовать. Пример такой концепции представлен в коде ниже: namespace Fona { using System; public class Plant { public Plant(){} public void BearFruit() { Console.WriteLine("Generic plant fruit"); } } class Tree : Plant { public Tree(){} // ключевое слово new используется здесь явно, чтобы скрыть // базовую версию new public void BearFruit() { Console.WriteLine("Tree fruit is:->Mango"); } } public class PlantEX { public PlantEX(){} public static void Main(String[] args) { Plant p = new Plant(); p.BearFruit(); // вызывает реализацию базового класса Tree t = new Tree(); t.BearFruit(); // вызывает реализацию класса наследника ((Plant)t).BearFruit(); // вызывает реализацию базового класса, // используя наследника. } } } Выполнение этого примера создает следующий вывод: Generic plant fruit Tree fruit is:->Mango Generic plant fruit Необходимо отметить, что существует различие между сокрытием метода и обыкновенным полиморфизмом. Полиморфизм всегда будет предоставлять для вызова метод класса-наследника.
Чтобы предоставить функциональность переопределения, используются модификаторы virtualи override. Все методы в базовом классе, которые будут переопределяться, должны применить ключевое слово virtual. Чтобы реально переопределить их, в классе-наследнике используется ключевое слово override. Ниже представлен пример класса Tree, измененный для вывода переопределенной функциональности: class Tree Plant { public Tree() {} public override void Fruit() { Console.WriteLine("Tree fruit is:->Mango"); } } Компиляция и выполнение этого создают следующий вывод. Generic plant fruit Tree fruit is:->Mango Tree fruit is:->Mango Как можно видеть, вызывается самый последний переопределенный метод Fruit()независимо от использования cтратегии преобразования ((Plant)t).BearFruit(), которая применялась ранее для ссылки на метод Fruit()базового класса. Модификатор new может также использоваться для сокрытия любого другого типа наследованных из базового класса членов аналогичной сигнатуры. Чтобы помешать случайному наследованию класса, используется ключевое слово sealed. В приведенном выше призере можно изменить объявление Plantна public sealed class Plantи в этом случае Treeбольше не сможет от него наследовать. C# не имеет модификатора native. В Java использование nativeуказывает, что метод реализован на зависимом от платформы языке. Это требует чтобы метод был абстрактным, так как реализация должна находиться в другом месте. Ближайшим к этому типу функциональности является модификатор extern. Использование externпредполагает, что код реализуется вовне (например, некоторой собственной DLL). Однако в отличие от Java, нет необходимости использовать ключевое слово abstractв соединении с ним. Фактически это приведет к ошибке, так как они означают две похожие, но различные вещи. Ниже класс Plantиз предыдущего примера показывает, как можно использовать extern: public class Plant : IfruitHaver { public extern int See(); public Plant(){} public void Fruit() { Console.WriteLine("Generic plant fruit"); } } Это не имеет большого смысла без использования атрибута DllImportдля определения внешней реализации. Более подробно атрибуты будут рассмотрены позже в приложении. Дальнейший код делает соответствующие изменения, предполагая, что функция Seeэкспортирована ресурсом User32.dll: public class Plant: IfruitHaver { [System.Runtime.InteropServices.DllImport("User32.dll)] public static extern int See(); public Plant(){} public void Fruit() { Console.WriteLine("Generic plant fruit"); } } Здесь метод See()помечен как статический. Атрибут DllImportтребует этого от методов, на которых он используется Пока не существует версии C# ключевых слов transient, volatileили synchronized. Однако существует ряд способов, предоставляемых SDK .NET для имитации некоторой их функциональности. C# использует атрибут NonSerialized, связанный с полями класса, для предоставления механизма, аналогичного по функциональности модификатору Java transient, этот атрибут однако опротестован, поэтому может быть изменен в будущих версиях. Синхронизация в C# несколько усложнена (более трудоемка) по сравнению с ее аналогом в Java. В общем, любой поток выполнения может по умолчанию получить доступ ко всем членам объекта. Имеется, однако ряд способов синхронизации кода в зависимости от потребностей разработчика с помощью использования таких средств, как Monitors. Они предоставляют возможность делать и освобождать блокировки синхронизации на объектах SyncBlocks, которые содержат блокировку используемую для реализации синхронизированных методов и блоков кода; список ожидающих потоков выполнения, используемых для реализации функциональности монитора ReaderWriterLock, который определяет образец одного писателя для многочисленных читателей; примитивы синхронизации Mutex, предоставляющие межпроцессную синхронизацию; и System.Threading.Interlocked, который может использоваться для предоставлении синхронизированного доступа к переменным через несколько потоков выполнения. Первый шаг к синхронизации в C# состоит в ссылке на сборку System.EnterpriseServices.dll. Инструкция lock(<expression>) {// блок кода}является единственным, связанным с синхронизацией, ключевым словом в C#. Оно может применяться, также как в Java, для получения взаимно исключающего доступа к блокировке объекта <ref>. Все попытки получить доступ к <expression>будут блокированы, пока поток выполнения с блокировкой не освободит ее. Обычно используется выражение либо this, либо System.Type, соответствующее Typeпредставленного объекта. Их использование будет защищать переменные экземпляра выражения в то время, как использование System.Typeбудет защищать статические переменные. Ключевые слова обработки ошибок: catch, finally, throw, throws, try Эти модификаторы являются одинаковыми в обоих языках, за исключением инструкции throws, которая отсутствует в C#. Пугающая вещь в отношении инструкции throwsиз Java состоит в том, что она позволяет потребителям компонента с помощью относительно простого синтаксиса использовать компонент, не зная какие исключения он может порождать. Можно удовлетвориться заверениями, что компилированный код обрабатывает все, имеющие отношение к делу, исключения, так как компилятор будет иначе отказывать и информировать обо всех не перехваченных исключениях. Функциональность такого рода отсутствует в C# в настоящее время. Предоставление метода потребителям сборки, желающем знать, когда порождаются исключения, должно будет привести к хорошей практике документирования или некоторому умелому программированию атрибутов. Выполнение вычислений может привести к сценарию, где вычисленный результат выходит за границы диапазона типа данных переменной результата. В Java, если целые значения достигают своих пределов, то они имеют неприятную особенность переходить к противоположной границе. Чтобы проиллюстрировать это, рассмотрим код следующего класса: // OverflowEX.java public class OverfTowEX { publiс static void main(String args []) { byte x = 0; for (int i = 0; i < 130; i++) { x++; System.out.println(x); } } } Как известно, byteв Java является 8-битовым типом данных со знаком. Это означает, что диапазон значений byteлежит от -128 до 128. Результатом добавления единицы к любой границе заданного диапазона целого типа будет другая граница диапазона целого типа. Поэтому в этом примере добавление 1 к 127 создаст -128. И если откомпилировать и выполнить эту программу, то последние пять чисел выведенные на консоли будут следующими: 126 127 -128 -127 -126 Это может оказаться весьма существенной проблемой, особенно в связи с тем, что ни предупреждение ни исключение не порождаются, чтобы позволить обработать такое событие (возможно, сохраняя значение в типе с большим диапазоном значений). По умолчанию C# также обрабатывает ситуации переполнения, но язык и компилятор предоставляют инструменты для явной обработки и уведомления программиста в случае переполнения. □ Программный подход. Чтобы бороться с этим типом молчаливых ошибок, C# вводит концепцию проверяемых и непроверяемых инструкций. Ключевое слово checkedиспользуется для управления контекстом проверки переполнения в случае операций и преобразований арифметики целого типа, таких, как представленные выше. Оно может использоваться как оператор или как инструкция. Инструкции checked/uncheckedследуют приведенному ниже синтаксису ( i). Они предназначены для помещения в скобки ряда инструкций, которые могут создавать переполнение. Синтаксис операции checked/uncheckedпоказан в пункте ( ii). Операция checked проверяет переполнение одиночных выражений: (i) checked {block_of_code} unchecked { block_of_code} (ii) checked (expression) unchecked (expression) block_of_codeсодержит код, в котором инструкция checked/uncheckedнаблюдает за переполнением, a expressionпредставляет выражение, в котором checked/uncheckedнаблюдает за переполнением в конечном значении. Показанный далее пример иллюстрирует использование checked/unchecked: // OverflowEX.cs public class OverflowEX { public static void Main(String() args) { sbyte x = 0; // помните, что необходимо изменить byte на sbyte for (int i = 0; i < 130; i++) { checked { // можно также использовать checked(x++) x++; Console.WriteLine(x); } } } } □ Подход с ключом компилятора. Для контроля переполнения во всем приложении может использоваться настройка компилятора /checked+. Чтобы проиллюстрировать это, удалим инструкцию checkedиз приведенного выше примера и попытаемся компилировать его, используя флаг /checked+. С его помощью можно включать и выключать проверку арифметического переполнения, изменяя состояние конфигурационных свойств на странице свойств проекта. Задание значения как true будет включать проверку переполнения. При включенной проверке переполнения можно обсудить использование инструкции unchecked. По сути она предоставляет функциональность для произвольного исключения проверки выражений или блоков инструкций в то время, когда включена проверка во всем приложении. В примере ниже предыдущая инструкция заменяется инструкцией unchecked. Компиляция и выполнение этого кода будет создавать вывод, аналогичный выводу OverflowEX.java. // OverflowEX.сs. public class OverflowEX { public static void Main(String[] args) { sbyte X = 0; for (int i = 0; i < 130; i++) { unchecked { // можно также использовать unchecked(x++) x++; Console.WriteLine(x); } } } } Входные и выходные данныеВозможность собрать входные данные из командной строки и вывести данные в командной строке является интегральной частью функциональности ввода/вывода в Java. Обычно в Java необходимо создать экземпляр объекта java.io.BufferedReader, используя поле System.in, чтобы извлечь ввод из командной строки. Ниже представлен простой класс Java — JavaEcho, который получает ввод с консоли и выводит его обратно, чтобы проиллюстрировать использование пакета Java.ioдля сбора и форматирования ввода и вывода: // JavaEcho.java import java.io.*; public class JavaEcho { public static void main(String[] args) throws IOException { BufferedReader stdin = new BufferedReader(new InputSreamReader(System.in)); String userInput = stdin.readLine(); System.out.println("You said: " + userInput); } } Класс System.Consoleпредоставляет методы в C#, которые дают аналогичную функциональность для чтения и записи из и в командную строку, Нет необходимости в каких-либо дополнительных объектах, класс Consoleпредоставляет методы, которые могут читать целые строки, читать символ за символом и даже показывать описанный выше поток, из которого выполняется чтение. Важно отметить, что эту функциональность дает System.Consoleбез создания экземпляра объекта Console. Фактически можно обнаружить, что невозможно создать экземпляр объекта Console. Члены класса Consoleкратко описаны в таблицах ниже:
Как можно видеть, все члены Consoleявляются статическими. staticявляется примером модификатора C#. Он обладает тем же значением, что и его аналог в Java, т.е. делает указанную переменную или метод принадлежащим всему классу, а не одному какому-то экземпляру класса. Мы обсудим модификаторы более подробно позже в этом приложении. С помощью мощных методов из класса Consoleможно записать эквивалент класса JavaEchoна C# следующим образом: class CSEchoer { static void Main(string[] args) { string userInput = System.Console.ReadLine(); System.Console.WriteLine("You said : " + userInput); } } Приведенный выше код значительно короче и легче, чем его аналог на Java. Статический метод Console.WriteLineпредоставляет одну полезную вещь, а именно, возможность использовать форматированные строки. Гибкость форматированных строк может быть проиллюстрирована написанием простой игры, где ввод пользователя применяется для создания рассказа. Код EchoGameпредставлен ниже: class EchoGame { static void Main(string[] args) { System.Console.WriteLine("Once upon a time in a far away" + "?"); string userInput1 = System.Console.ReadLine(); System.Console.WriteLine("a young prince ?"); string userInput2 = System.Console.ReadLine(); System.Console.WriteLine("One day while?"); string userInput3 ? System.Console.ReadLine(); System.Console.WriteLine("He came across a ?"); string userInput4 = System.ConsoleReadLine(); System.Console.WriteLine("The prince ?"); String userInput5 = System.Console.ReadLine(); System.Console.WriteLine("Once upon a time in a far away" + " {0}, a young prince {1}. \n One day" + "while {2}, He came across a (3). \n The " + "prince {4} ! ", userInput1, userInput2, userInput3, userInput4, userInput5); } } Точки вставки заменяются предоставленными аргументами, начиная с индекса {0}, который соответствует самой левой переменной (в данном случае userInput1). Можно подставлять не только строковые переменные, и не обязательно использовать только переменные или переменные одного типа. Любой тип данных, который может вывести метод WriteLine, допустим в качестве аргумента, включая строковые литералы или реальные значения. Не существует также ограничений на число точек вставки, которые могут добавляться к строке, пока их не больше общего числа аргументов. Отметим, что исключение точек вставки из строки приведет к тому, что переменные не будут выводиться. Необходимо, однако, иметь аргумент для каждой определенной точки вставки, индекс которой в списке аргументов соответствует индексу точки вставки. В следующем листинге, например, удаление {1}допустимо, пока имеется три аргумента. В этом случае {0}соответствует strAи {2}соответствует strC: Console.WriteLine("hello {0} {1} {2}", strA, strB, strC); КомпиляцияПри описании некоторых различий между JRE Java и CLR C# кратко упоминались некоторые детали того, как написанный на соответствующем языке код компилируется и выполняется. Хотя код обоих языков компилируется в некоторую промежуточную форму, байт-код версии Java никогда не компилируется повторно в собственные инструкции машины (если только не используется компилятор в собственный код). Вместо этого байт-код требует для выполнения среду времени выполнения, и в частности, виртуальную машину. Имя компилированного файла связано с именем файла, в котором находится исходный код, который в свою очередь связан с именем открытого класса в этом файле. В случае определения нескольких классов в одном файле каждое определение класса будет создавать файл класса, который соответствует имени определенного класса. Например, возьмем исходный файл Test.javaсо следующим кодом: // Test.java class x {} class у {} class z {} Компиляция этого файла будет создавать три файла классов: х.class, у.class, z.class. Один (и только один для каждого исходного файла) из этих классов может быть объявлен открытым следующим образом: // Test.java public class x {} class у {} class z {} В приведенном выше примере имя исходного файла Test.javaдолжно быть изменено, чтобы соответствовать имени находящегося в нем открытого класса. Test.javaпоэтому станет х.java, чтобы код компилировался. В противоположность этому, компилированный в IL код C# выполняется VES (Virtual Execution System), которая предоставляет поддержку для IL, загружая управляемый код, и JITters (которые преобразуют управляемый код в форме промежуточного языка в собственный код машины). Имя файла Hello.csне связано с именем конечного исполняемого файла и может изменяться во время компиляции с помощью ключа /out. Если имя файла вывода не определено, то exe получит имя того файла исходного кода, который содержит метод main, a DLL получит имя первого указанного файла исходного кода. Фактически имя файла даже не связано с определениями любых классов, находящихся в файле. Класс Helloможет быть определен в файле Goodbу.cs, который компилируется в несвязанный MisterHanky.exe. SDK .NET поставляется вместе с компилятором, поэтому не нужно беспокоиться о получении специальных компиляторов для этого приложения. Откройте просто командную строку, перейдите в каталог, где сохранен файл hello.cs, и введите: csc hello.cs Файл будет компилирован в hello.exe. Хотя большинство программистов Java знакомы с этой формой низкоуровневой компиляции, важно заметить, что Visual Studio.NET предоставляет аналогичную функциональность, интегрированную в IDE. Например, изменение имени исполнимого файла можно легко выполнить, добавляя свойство имени сборки страницы свойств проекта. Чтобы сделать это производим щелчок правой кнопкой мыши на имени проекта в панели Solution Explorer и выбираем Properties или указываем Project в меню, в то время, когда имя проекта выделено, и выбираем пункт меню Properties. В папке Common Properties можно выбрать страницу General Properties и изменить свойство Assembly Name. Можно будет увидеть, что свойство для чтения Output File изменяется, отражая новое имя сборки. Типы компиляции Все файлы Java компилируются в файл байт-кода с расширением .class, который может выполниться виртуальной машиной. Внутри первого исходного кода необходимо предоставить соответствующую. Функциональность для создания того или иного типа приложения. Например, определенный ниже код будет создавать окно, аналогичное Formв Windows. Второй исходный файл AddLib.javaявляется вспомогательным классом, используемым для выполнения сложения двух целых чисел. Можно заметить, что они включены в отдельные пакеты и JavaFrameимпортирует класс AddLib. Пакеты и их эквивалент C# будут рассмотрены в следующем разделе: // код для JavaFrame.java Package com.javaapp; import java.awt.*; import java.io.*; import com.javalib.AddLib; public class JavaFrame extends java.awt.Frame { public static void main (String[] args) { JavaFrame jfrm = new JavaFrame(); jfrm.setSize(100, 100); jfrm.setVisible(true); AddLib lib = new AddLib(); jfrm setTitle("Frame Version " + lib.operationAdd(12, 23)); } } // код для AddLib.java Package com.javalib; public class AddLib { public AddLib() { } public int operationAdd(int a, int b) { return a + b; } } Java предоставляет двухшаговый процесс для создания исполнимого файла или библиотечных единиц компиляции: откомпилировать файл(ы) и сделать файл доступным, предоставляя компилятору путь доступа к папке, где находятся файлы (можно написать пакетный файл, который будет делать это за один шаг). Класс можно сделать доступным с помощью ключа компилятора -classpathдля определения, где компилятор может искать разрешение символов, не определенных в исходном коде (различные компиляторы могут иметь различные имена для этого ключа). Система имеет также переменную среды окружения Classpath. Если ключ -classpathне определен, то компилятор будет искать переменную окружения, если ключ -classpathопределен, то он переопределяет любые записи Classpath, которые могут существовать среди переменных окружения для этой специфической компиляции. Классы могут также объединяться в файл JAR, который необходимо сделать доступным таким же образом, как и папку, содержащую в себе класс. Классы внутри или вне файла JAR могут быть или не быть частью нуля или большего числа пакетов. В приведенном выше примере JavaFrameи AddLibнеобходимо будет откомпилировать в файлы классов. Путь доступа к этим файлам классов можно затем добавить к переменной окружения CLASSPATH. Когда путь доступа к классам задан, любые классы из пакета могут выполняться из любого каталога в системе, передавая полностью квалифицированное имя класса в виртуальную машину. Вызов с помощью SUN Microsystems JDK 1.3: java javalib.JavaFrame приводит к выполнению программы JavaFrameи созданию Frameс панелью заголовка, выводящей Java Frame Version 35. Код в C# всегда после компиляции автоматически объединяется в тот или иной тип компонента. Единицы компиляции могут содержать столько файлов и определений классов, сколько потребуется. Способ использования этой функциональности снова делится между использованием командной строки и использованием IDE (в частности, Visual Studio.NET). Создание нового проекта с помощью VS.NET требует, чтобы был определен тип создаваемого проекта. Среди других имеются возможности создать консольное приложение, оконное приложение и библиотеку классов. Можно создать даже пустой проект и определить тип вывода позже. Процесс определения типа вывода проекта с помощью VS.NET описан дальше в этом разделе. В командной строке для изменения типа вывода используется ключ /target: <target-type>, где target-typeявляется одной из строк: □ Ехе □ Library □ Winexe Можно добавить любое число файлов как аргументы, разделенные пробелами: csc /target:<target-type> <file1> <file2> ... <filen> Добавление нескольких файлов в единицу компиляции с помощью VS.NET является вопросом добавления отдельных файлов в проект. Можно легко изменить тип выходного файла, поменяв свойство Output Type на странице General Properties (смотрите выше детали доступа к странице с общими свойствами). Пространства именЦель данного изложения состоит в создании версии C# файлов исходного кода JavaFrameи AddLibи рассмотрении деталей процесса создания кода C#. Так как два эти класса используют пакеты и импортирование, необходимо обсудить их эквиваленты в C#. Классы Java могут располагаться в логических подразделениях, называемых пакетами. Пакет определяется как сущность, которая группирует классы вместе. Пакеты могут облегчить импорт другого кода программиста и более важно определить ограничения доступа к переменным и методам. Пространства имен в C# предоставляют аналогичный механизм для объединения управляемых классов, но являются значительно более мощными и гибкими. Здесь речь идет об управляемых классах, а не специальных классах C#, так как классы в пространстве имен могут быть из любого, соответствующего CLS, языка (вспомните, что CLS является независимым от языка). Пакеты и пространства имен, однако, существенно различаются своей реализацией. Класс Java, который необходимо, например, сделать частью пакета com.samples, должен иметь в качестве первой строки кода в файле Package com.samples. Это, конечно, исключает какие-либо комментарии. Любой код внутри этого файла автоматически становится частью указанного пакета. Имя пакета в Java также связывается с папкой, содержащей файл класса в том смысле, что они должны иметь одинаковые имена. Пакет com.samplesпоэтому должен находиться в файле, который существует в папке com\samples. Давайте рассмотрим некоторые примеры того, как работают пакеты. // package_samples.java package samples.on; // отображает прямо в папку, где находится файл класса public class Packaging { int x; public class Internal { // находится автоматически в том же пакете } public static void main(String args[]) { } } class Internal { // находится автоматически в том же пакете } Примеры того, как этот код может выполняться, приведены ниже. Это, конечно, предполагает, что файл класса был сделан доступным для JRE: □ Из командной строки: java samples.on.Packaging □ Как непосредственная ссылка в коде: // Referencer.java public class Referencer { samples.on.Packaging pack = new samples.on.two.three.Packaging(); □ Используя директиву import, можно опустить полностью квалифицированные имена пакетов, поэтому Referencerзапишется как: // Referencer.java import samples.on.*; public class Referencer{ Packaging pack = new Packaging(); } Помещение класса в пространство имен достигается в C# с помощью ключевого слова namespaceс идентификатором и заключением целевого класса в скобки. Вот пример: // namespace_samples.cs namespace Samples.On { using System; public class Example { public Example() { } } } Преимущество использования скобок для явного ограничения пространства имен состоит в том, что это задает определенный пользователем тип в реальном классе, определенном в файле, а не в самом файле. В Java файлы и папки косвенно представляют структуры языка, так как они аналогичны классам и пакетам, содержащим эти классы. В C# файлы не связаны принудительно с чем-либо, поэтому они становятся местом, где располагается определение класса, а не частью какой-либо структуры языка. Пространства имен также не связаны с папками. Следовательно, в одном файле можно ввести несколько пространств имен без всяких ограничений. Можно, например, добавить определение нового класса и поместить его в новое пространство имен в том же файле и по-прежнему оставаться в границах языка: // namespace_samples.cs namespace Samples.On { using System; public class Example { public Example() { } } } namespace Com.Cslib { using System; using System.Collections; public class AddLib { public AddLib() { } public int operationAdd(int a, int b) { return a + b; } } } Пространства имен вводятся с помощью директивы using <namespace name>, где <namespace name>является именем пространства имен. В C# не требуется использовать *, так как директива usingнеявно импортирует все элементы указанного пространства имен. Другим преимуществом является то, что пространства имен могут быть добавлены исключительно в конкретный класс. Хотя классы Exampleи AddLibвыше определены в файле namespace_samples.cs. Exampleне имеет доступа к пространству имен System.Collections, несмотря на то, что AddLibего имеет. Однако инструкция importиз Java не является специфической для класса. Она импортирует указанные элементы в файл. Вновь обратимся к х.java. // х.java public class x { } class у { } class z { } Если добавить инструкцию импорта, такую как import java.util.Hashtable, все классы, определенные внутри этого файла, будут иметь доступ к классу Hashtable. Код ниже будет компилироваться: // x.java package samples; import java.util.Hashtable; public class x { Hashtable hash = new Hashtable(); } class у { Hashtable hash = new Hashtable(); } class z { Hashtable hash = new Hashtable(); } Пространства имен можно также определять внутри другого пространства имен. Этот тип гибкости недоступен в Java без создания подкаталогов. Приведенное выше пространство Com.Cslibможно расширить следующим образом: namespace Com.Cslib { using System; public class AddLib { public AddLib() { } public int operationAdd(int a, int b) { return a + b; } } namespace Ext { public class AddLib { public AddLib() { } public int operationAdd(int a, int b) { return a + b; } } } } Пакет Java com.javalibможно расширить, чтобы отобразить приведенный выше код, создав новую папку \EXTв каталоге com\javalib. В этой папке создается файл исходного кода AddLib.javaследующим образом: package com.javalib.ext; public class AddLib { public AddLib() { } public int operationAdd(int a, int b) { return a + b; } } Отметим, что имя пакета было расширено для этого класса до com.javalib.ext. Внутреннее пространство имен и подпакеты доступны с помощью оператора точки "."; следовательно, можно было в C# извлечь расширенный AddLib с помощью нотации Com.Cslib.Ext.AddLib. В Java можно было бы использовать com.javalib.ext.AddLib. Приведенный выше пример показывает одно сходство между пакетами Java и пространствами имен C#. Даже если они не используются для внешнего представления, пространства имен, так же как и пакеты, предоставляют прекрасный способ создания глобально уникальных типов, свою собственную песочницу в мире сборок независимых поставщиков. В той степени насколько это имеет отношение к C#, Com.Cslib.AddLibявляется не тем же классом, что и Com.Cslib.Ext.AddLib. Классы Java являются частью пакета, нравится им это или нет. Все классы, созданные без указания пакета, предполагают включение в пакет по умолчанию. C# имитирует эту функциональность. Даже если не объявить пространство имен, оно будет создано по умолчанию. Оно присутствует в каждом файле и доступно для использования в именованных пространствах имен. Так же как в Java нельзя изменить информацию о пакете, пространства имен нельзя модифицировать. Пакеты могут охватывать несколько файлов в одной папке, пространство имен может охватывать несколько файлов в любом числе папок и даже в нескольких сборках (сборки будут рассмотрены в следующем разделе). Два класса, охватываемые пространством имен А, которые определены в отдельных файлах и существуют в отдельных папках, оба являются частью пространства имен А. Чтобы получить доступ к элементу в пространстве имен, необходимо либо использовать полностью квалифицированное имя типа (в приведенном выше примере это Com.Cslib.AddLib) или импортировать элемент пространства имен в текущее пространство имен, используя директиву using. Отметим, что по умолчанию доступность типов данных внутри пространства имен является внутренней. Необходимо явно отметить типы данных как открытые ( public), если требуется сделать их доступными без полной квалификации, но придерживаться такой стратегии строго не рекомендуется. Никакие другие модификаторы доступа не разрешены. В Java внутренние типы пакета могут также помечаться как finalили abstract, или не помечаться вообще (этот доступ по умолчанию открывает их только для потребителей внутри пакета). Модификаторы доступа будут рассматриваться позже в этом приложении. Последним атрибутом, который относится к пространству имен, но не имеет отношения к пакетам, является возможность задания алиаса для using. Алиасы usingсущественно облегчают квалификацию идентификатора для пространства имен или класса. Синтаксис очень простой. Предположим, что имеется пространство имен Very.Very.Long.Namespace.Name. Можно определить и использовать алиас usingдля пространства имен следующим образом: using WLNN = Very.Very.Long.Namespace.Name; Конечно, имя псевдонима (алиаса) является произвольным, но должно следовать правилам именования переменных в C#. Создание и добавление библиотек при компиляцииРанее, при обсуждении компиляции и единиц компиляции кратко было сказано о концепции библиотек. Если создана библиотека, то необходимо, чтобы она была доступна для всех потенциальных потребителей. В Java это делается добавлением пути доступа к папке, содержащей классы библиотеки, в переменную окружения Classpath. Конечно, чтобы упростить это, можно добавить файлы класса в папке в JAR и поместить путь доступа к файлу jarв Classpath. В любом случае работа загрузчика классов состоит в нахождении всех неразрешенных ссылок, и он будет искать их в Classpath. C# предоставляет совсем другие механизмы для упаковки классов в библиотеку. По умолчанию все файлы C# в проекте станут частью единицы компиляции при использовании VS.NET. Если применяется командная строка, необходимо будет явно добавлять каждый файл, который должен быть частью единицы компиляции, как описано выше. Библиотеки кодов компилируются в РЕ (portable executable — переносимый исполнимый) тип файла. Необходимо в этом месте сделать различие между библиотеками кодов, о которых идет речь, и проектом Class Library в C#, который может создаваться с помощью IDE VS.NET. Под библиотекой кода понимается повторно используемое множество файлов C#, соединенных вместе в некоторой единице компиляции, поэтому происходит ссылка на файл РЕ, а не на реальную DLL или ЕХЕ. Библиотека кода такого вида более часто называется сборкой, поэтому это название будем использовать в дальнейшем, чтобы избежать путаницы. Так же как файл JAR, сборка имеет манифест, который описывает ее содержимое. Манифест сборки C# делает, однако, значительно больше. Он содержит все метаданные (совокупность данных, описывающая, как связаны элементы сборки), необходимые для определения требований версии, идентичности безопасности и всей информации, служащей для определения области действия сборки и разрешения ссылок на ресурсы и классы. Манифест сборки может храниться либо в файле РЕ (ЕХЕ или DLL) с кодом IL, либо как автономный файл, который содержит только данные манифеста сборки. В .NET пространства имен, содержащиеся внутри сборки, представлены внешнему миру (то есть, любым потребителям сборки, таким, как другие сборки) с помощью информации метаданных о типах, хранящейся в манифесте сборки. Классы в namespace_samples.csмогут компилироваться в библиотеку с помощью команды: CSC /target:library /out:FirstLibrary.dll namespace_samples.cs Чтобы сделать информацию о типах из одной сборки доступной для других сборок, можно использовать два ключа компилятора: /addmoduleи /reference. Они являются по сути одинаковыми, за исключением того, что /referenceпредназначен для сборок с манифестом сборки, в то время как /addmoduleпредназначен для сборок без манифеста сборки (модуль не имеет манифеста сборки). Синтаксис для добавления внешних ссылок из командной строки будет следующим: csc /reference: <lib.dll>; <libn.cs> <filename.exe> или: csc /addmodule: <lib.dll>; <libn.cs> <filename.exe> Чтобы добиться этого с помощью VS.NET, сделайте щелчок правой кнопкой мыши на папке References своего проекта в Solution Explorer и выберите Add Reference. Появится диалоговое окно, содержащее ряд доступных ссылок и позволяющее просматривать файловую систему в поисках ссылок. Можно также вызвать это диалоговое окно, выбирая пункт Add Reference из меню Project, когда выделен требуемый проект. Добавление ссылки в текущую сборку обычно копирует упомянутый файл в папку проекта, где будет располагаться новая сборка. Давайте создадим теперь потребителя для библиотеки AddLib, который аналогичен по форме и функциональности созданному ранее классу JavaFrame: // LibConsumer.cs namespace Com.CSapp { using System; using System.Windows.Forms; using Com.CsLib; public class Form1 : System.Windows.Forms.Form { public Form1() { AddLib al = new AddLib(); this.Text = "C# Form version " + al.operationAdd(12, 23); } public override void Dispose() { base.Dispose(); } Static void Main(string[] args) { Form1 f1 = new Form1(); Application.Run(f1); } } } Этот код можно компилировать из командной строки со ссылкой на FirstLibrary.dll, введя следующую команду: csc /reference: FirstLibrary.dll /target:exe /out:EmptyForm.exe LibConsumer.cs Используя VS.NET, надо сначала сослаться на FirstLibrary.dll, как описано ранее, и затем собрать приложение. Когда приложение будет успешно собрано, выполните его. Оно должно создать форму Windows с заголовком C# form Version 35. Обнаружение и разрешениеМы уже обсудили, как JRE разрешает ссылки на другие классы, используя загрузчик классов для проверки во время выполнения переменной окружения Classpath. CLR проходит также ряд шагов, часто называемых зондированием (probing), при попытке найти сборку и разрешить ссылку на сборку. Попытка выполнить EmptyForm.exeприводит в движение ряд вещей. По умолчанию весь управляемый код загружается в домен приложения и обрабатывается определенным потоком выполнения операционной системы. Указанные сборки, означающие, что их типы используются в коде домена приложения, также должны загружаться, прежде чем их можно будет выполнить. CLR проходит несколько стадий, чтобы обнаружить и связаться с указанной сборкой. Так как поведение по соединению со сборками может конфигурироваться на основе конфигурационных файла приложения, файла издателя и файла машины/администратора, CLR должна брать эту конфигурационную информацию, чтобы обеспечить извлечение соответствующей версии указанной сборки. Все эти файлы основаны на XML и следуют похожему синтаксису. Они предоставляют такую информацию, как перенаправление связывания, расположение кода и режимы связывания для определенных сборок. Обычно правильная версия сборки определяется некоторой комбинацией трех конфигурационных файлов и собственным манифестом сборки. CLR сначала проверяет, не переопределяет ли информация конфигурационного файла приложения информацию, которая хранится в манифесте вызывающей сборки. Затем CLR проверяет конфигурационный файл издателя. Этот файл присутствует, только когда приложение было обновлено новыми версиями одного или нескольких компонентов приложения. Это прежде всего используется для переопределения информации в конфигурационном файле приложения, чтобы приложение выбрало новую версию компонента. CLR затем проверяет конфигурационный файл машины/администратора. Хотя он просматривается последним, настройки, присутствующие в этом файле, получают приоритет по отношению ко всем другим конфигурационным настройкам. По сути администраторы используют admin.cfg, чтобы определить ограничения связывания, локальные для данной машины. Затем CLR необходимо найти сборку. В Java JRE будет действовать в текущем каталоге и в classpass, чтобы найти класс, необходимый для разрешения ссылки. В определении расположения заданной сборки CLR полагается на элемент <codeBase>, связанный с упомянутыми выше конфигурационными файлами. Если такой элемент не предоставлен, то CLR ищет файл (называемый probing) в корневом каталоге приложения, во всех каталогах, перечисленных в конфигурационных файлах элемента <probing>и во всех подкаталогах корня приложения, которые имеют такое же имя, как и зондируемая сборка. Эти элементы всегда определяются относительно корневого каталога приложения. Отметим, что CLR всегда ищет имя сборки, соединенное с двумя допустимыми расширениями файлов РЕ — .exeи .dll. Наконец, если ссылка сделана на сборку с устойчивым именем, CLR будет искать в глобальном кэше сборок. Устойчивые имена и глобальный кэш сборок мы рассмотрим более подробно в следующем разделе. Строгие имена и глобальный кэшУстойчивое (строгое) имя делает сборку глобально уникальной. Оно состоит из идентификатора сборки, открытого ключа и цифровой подписи. Так как сборка, созданная с одним открытым ключом имеет другое имя, чем сборка, созданная с другим открытым ключом, то уникальность имени сборки гарантирована. Нельзя изменить сборку с устойчивым именем, не имея доступа к открытому ключу, использованному при ее создании, следовательно, устойчивое имя гарантирует, что никто не сможет создать другие версии этой сборки. Устойчивые имена предоставляют также жесткий контроль целостности. Создание устойчивого имени является двухшаговым процессом. Сначала необходимо создать или иметь ключевую пару. Создание ключевой пары можно выполнить в командной строке с помощью команды: sn -k <имя файла ключа> Например, чтобы создать файл ключа examplekey.key, надо ввести: sn -k examplekey.key После создания файл ключа используется для присвоения устойчивого имени сборке двумя способами. Из командной строки можно использовать утилиту alinkследующим образом: al /keyfile: <имя файла ключа> <имя сборки> Чтобы задать для FirstLibrary.dllстрогое имя, можно выполнить: al /keyfile:examplekey.key FiretLibrary.dll или в VS.NET изменить атрибут [assembly: AssemblyKeyFile("")]в файле AssemblyInfo.cs. Замените просто пустую строку на строку, представляющую имя файла ключа. Сборку FirstLibrary.dllможно затем ассоциировать с ключом, меняя атрибут AssemblyKeyFileна [assembly: AssemblyKeyFile("examplekey.key")]. Какой бы метод ни использовался, конечный результат получается тот же самый. CLR будет устанавливать ключ в файл с помощью Crypto Service Provider (CSP). Работа CSP не представлена в данном приложении. Все сборки в глобальном кэше сборок должны иметь устойчивое имя. Глобальный кэш сборок применяется для хранения сборок, специально созданных для общего использования несколькими приложениями на машине. Существует несколько способов размещения сборки в глобальном кэше сборок. Можно использовать программу установки, созданную для работы с глобальным кэшем сборок, .NET Framework SDK предоставляет программу установки с именем gacutil.exe. Чтобы поместить FirstLibrary.dllв глобальный кэш сборок, воспользуйтесь командой: gacutil -i FirstLibrary.dll Можно использовать Проводник Windows для перетаскивания сборок в кэш. Отметим, что необходимо иметь привилегии администратора на машине, чтобы устанавливать сборки в глобальный кэш сборок, независимо от используемого подхода. Типы данныхТипы данных в Java и в C# можно сгруппировать в две основные категории: типы данных значений и ссылочные типы. Существует только одна категория типа данных значений в Java. Все типы данных значений являются по умолчанию примитивными типами данных языка. C# предлагает более обильный ассортимент. Типы данных значений можно разбить на три основные категории: □ Простые типы □ Типы перечислений □ Структуры Давайте рассмотрим каждый из них по очереди. Простые типыРанее в разделе о ключевых словах было сделано подробное сравнение между примитивными типами данных Java и их эквивалентами в C# (по размеру). Был также введен ряд типов данных значений, представленных в C#, которых Java не имеет. Это были 8-битовый без знака byte(отличный отbyte в Java, который имеет знак и отображается в sbyteв C#), короткое целое без знака ushort, целое без знака uint, длинное целое без знака ulongи, наконец, высокоточное decimal. Целые значения Когда целое число не имеет суффикса, то тип, с которым может быть связано его значение, оценивается в порядке int, uint, long, ulong, decimal. Целые значения представляются как десятичные или шестнадцатеричные литералы. В коде ниже результат равен 52 для обоих значений: int dec = 52; int hex = 0x34; Console.WriteLine("decimal {0}, hexadecimal {1}", dec, hex);Символьные значения charпредставляет одиночный символ Unicode длиной два байта. C# расширяет гибкость присваивания символов, допуская присваивание с помощью шестнадцатеричной кодированной последовательности с префиксом \хи представление Unicode с помощью \u. Также нельзя неявно преобразовать символы в целые числа. Все другие обычные кодированные последовательности языка Java полностью поддерживаются. Логические значения bool, booleanв Java, используются для представления значений trueи falseнепосредственно или как результат равенства, как показано ниже: bool first_time = true; cool second_time = (counter < 0);Значения decimal C# вводит тип данных decimal, который является 128-битовым типом данных, представляющим значения в диапазоне от примерно 1.0×1028 до 7.9×1028. Они предназначены прежде всего для финансовых и денежных вычислений, где точность является предельно важной. При присваивании типу decimalзначения, к литеральному значению должно добавляться m, иначе компилятор считает значение типом double. Так как decimalне может неявно преобразовываться в double, то отсутствие m требует явного преобразования типа: decimal precise = 1.234m; decimal precise = (decimal)1.234;Значения с плавающей точкой Значения с плавающей точкой могут быть либо double, либо float. При вычислениях все другие простые типы значений будут неявно преобразовываться, в соответствующий тип с плавающей точкой, если присутствует тип с плавающей точкой. Действительный числовой литерал с правой стороны оператора присваивания интерпретируется как doubleпо умолчанию. Так как не существует неявного преобразования из floatв double, может оказаться удивительным возникновение ошибок компиляции. Пример ниже иллюстрирует эту проблему: float f = 5.6; Console.WriteLine(x); Этот пример будет создавать сообщение об ошибке компиляции, показанное ниже. C:\_wrox\c# for java developers\code\SuperEX\Class1.cs(15): Literal of type double cannot De implicitly converted to type 'float'; use an 'F' suffix to create a literal of this type Существует два способа решения этой проблемы. Можно преобразовать литерал во float, но сам компилятор предлагает более разумную альтернативу. Использование суффикса Fговорит компилятору, что это литерал типа float, а не double. Хотя и не обязательно, но можно использовать суффикс D, чтобы указать на литерал типа Double. Типы перечисленийПеречисление является отдельным типом, состоящим из множества именованных констант. В Java можно добиться этого, используя переменные static final. В этом смысле перечислении могут в действительности быть частью класса, который их использует. Другой подход состоит в определении перечисления как интерфейса. Пример ниже иллюстрирует такую концепцию: interface Color { static int RED = 0; static int GREEN = 1; static int BLUE = 2; } Этот подход проблематичен тем, что он не является безопасным в отношении типов данных. Любое считанное или вычисленное целое используется в качестве цвета. Можно, однако, программным путем реализовать перечисление с безопасными типами в Java, используя вариант шаблона (паттерна) Singleton, который ограничивает класс предопределенным числом экземпляров. Приведенный далее код показывает, как это можно сделать: final class Day { // final, поэтому нельзя создавать подклассы этого класса private String internal; private Day(String Day) {internal = Day;} // закрытый конструктор public static final Day MONDAY = new Day("MONDAY"); public static final Day TUESDAY = new Day("TUESDAY"); public static final Day WEDNESDAY = new Day("WEDNESDAY"); public static final Day THURSDAY = new Day("THURSDAY"); public static final Day FRIDAY = new Day("FRIDAY"); } Как можно видеть из приведенного выше примера, перечисленные константы связаны не с примитивными типами, а с объектными ссылками. Также, поскольку класс определен как final, то нельзя создать его подклассы, следовательно, никакие другие классы не могут из него создаваться. Конструктор отмечен как закрытый, поэтому другие методы не могут использовать класс для образования новых объектов. Единственные объекты, которые будут созданы для этого класса, это статические объекты, которые класс формирует для себя при первой ссылке на класс. Можно согласиться с тем, что хотя концепция достаточно простая, обход включает развитую технику и она может не сразу стать понятной новичку, в конце концов нам требуется только список констант; C#, в противоположность, предоставляет встроенную поддержку перечислении, которая обеспечивает также безопасность типов. Чтобы объявить в C# перечисление, используется ключевое слово enum. В своей простейшей форме enumможет выглядеть как следующий код: public enum Status { Working, Complete, BeforeBegin } В приведенном выше случае первое значение равно 0 и enumувеличивает значения. Completeбудет 1 и т.д. Если по какой-то причине требуется, чтобы enumпредставлял другие значения, можно сделать это, присваивая такие значения следующим образом: public enum Status { Working = 131, Complete = 129, BeforeBegin = 132 } Имеется также возможность использовать другие числовые целые типы 'наследуя' от long, shortили byte. intвсегда является типом по умолчанию. Эта концепция проиллюстрирована ниже: public enum Status : int { Working, Complete, BeforeBegin } public enum SmallStatus : byte { Working, Complete, BeforeBegin } public enum BigStatus : long { Working, Complete, BeforeBegin } Обратите внимание, что существует большое различие между этими тремя перечислениями, связанное напрямую с размером типа данных, от которого они наследуют. byteв C#, например, может содержать 1 байт памяти. Это означает, что SmallStatusне может с одержать более 255 констант или задать значение любой своей константы больше 255. Следующий листинг показывает, как можно использовать оператор sizeof()для идентификации различий между версиями Status: int х = sizeof(Status); int у = sizeof(SmallStatus); int Z = sizeof(BigStatus); Console.WriteLine("Regular size:\t{0}\nSmall size \t{1}\nLarge size:\t{2}", x, y, z); После компиляции листинг создаст результаты, показанные ниже: Regular size: 4 Small size: 1 Large size: 8 СтруктурыОдним из основных различий между структурой C# (идентифицируемой ключевым словом struct) и классом является то, что то умолчанию structпередается посредством значения, в то время как объект передается по ссылке. Как хорошо известно, объекты создаются в куче, в то время как переменные, которые на них ссылаются, хранятся в стеке. Структуры, со своей стороны, создаются и хранятся в стеке. Их аналога в Java не существует. Структуры имеют конструкторы и методы, у них могут быть индексаторы, свойства, операторы и даже вложенные типы. С помощью struсtсоздаются типы данных, которые ведут себя таким же образом, как встроенные типы. Ниже приведен пример использования структур: public struct WroxInt { int internalVal; private WroxInt(int x) { internalVal = x; } public override string ToString() { return Int.ToString(internalVal); } public static impicit operator WroxInt(int x) { return new WroxInt(x); } } public static void UseWroxInt() { WroxInt wi = 90; Console.WriteLine(wi); } Этот пример показывает типы, которыми владеют мощные структуры. WroxIntиспользуется почти так же, как и встроенный тип int. Как известно, не существует способа сделать что-нибудь подобное в Java. Ряд других достоинств и ограничений, связанных с использованием структур, представлен ниже: □ structнельзя наследовать от другой structили от класса. □ structне является базой для класса □ Хотя structможет oбъявлять конструкторы, эти конструкторы должны получать не меньше одного аргумента. □ Члены structне могут иметь инициализаторов. □ Возможно создание экземпляра structбез использования ключевого слова new. □ structможет реализовывать интерфейсы. Атрибуты используются со структурами чтобы добавить им дополнительную мощь и гибкость. Атрибут StructLayoutв пространстве имен System.Runtime.InteropServices, например, применяется для определения компоновки полей в struct. Это свойство подходит и для создания структуры, аналогичной по функциональности unionв С/C++, unionявляется типом данных, члены которого находятся в одном блоке памяти. Он может использоваться для хранения значений различных типов в одном блоке памяти. unionгодится и в том случае, когда неизвестно, каким будет тип полученных значений. Конечно, никакого рeaльного преобразования не происходит, фактически не существует никакие базовых проверок допустимости данных. Один и тот же набор битов интерпретируется различным образом. Рассмотрим пример того, как unionсоздается с помощью struct: [StructLayout(LayoutKind.Explicit)] public struct Variant { [FieldOffset(0)] public int intVal; [FieldOffset(0)] public string stringVal; [FieldOffset(0)] public decimal decVal; [FieldOffset(0)] public float floatVal; [FieldOffset(0)] public char charVal; } Атрибут FieldOffset, применяемый к полям, используется для задания физического расположения указанного поля. Задание начальной точки каждого поля как 0 гарантирует, что любое сохранение данных в одном поле перезапишет практически любые данные, которые там находятся. Отсюда следует, что общий размер полей равен размеру наибольшего поля, в данном случае decimal. Ссылочные типыСсылочные типы хранят ссылку на данные, которые существуют в куче. Только адреса памяти хранимых объектов сохраняются в стеке. Тип объекта, массивы, интерфейсы тип класса и делегаты являются ссылочными типами. Объекты, классы и отношения между ними не отличаются в Java и C#. Интерфейсы и их использование также похожи в обоих языках. Одно из основных различий, которое, вероятно, уже встречалось, состоит в том, что C# не имеет ключевых слов extendsи implements. Оператор двоеточия ( :) заменяет оба ключевых слова Java, и, как было показано ранее, директива usingаналогична инструкции Java import. Строки тоже используются одинаково в C# и Java. C# вводит также новый тип ссылочного типа называемого делегатом. Делегаты представляют безопасную, с точки зрения типов, версию указателей функций. Они будут рассмотрены позже в этой главе. Массивы C# поддерживает "неровные" массивы и добавляет многомерные массивы. Может сбить с толку то, что Java не делает между ними различий: int [] х = new int[20]; // как в Java, только [] должны следовать // за типом int [,] у = new int[12, 3]; // то же самое, что int у[] [] = new // int[12][3]; int[][] z = new int[5][]; // то же самое, что и int x[][] = new // int [5][]; Примечание. Ключевое слово int[]обозначает реальный тип данных, поэтому синтаксически оно записывается таким образом. Нельзя, как в Java, поместить двойные скобки перед или после переменной. Прежде чем перейти к дополнительным деталям о ссылочных типах и обсуждению таких концепции, как классы, давайте поговорим немного об операциях. Следующий раздел посвящен операторам. ОператорыВ Java конечный результат применения оператора к одному или нескольким операндам является новым значением для одного или нескольких вовлеченных операндов. C# предоставляет аналогичную функциональность, однако, как можно будет увидеть в разделах ниже, существуют незначительные различия между C# и Java даже в этой области. В этом разделе мы охватим разные группы операторов в C# и обсудим чем отличается в C# и Java каждая группа. ПрисваиваниеC# и Java используют знак =для присваивания значений переменным. В C#, как и в Java, переменные присвоенные объектам содержат только ссылку или "адрес" на этот объект, а не сам объект. Присваивание одной ссылочной переменной другой таким образом просто копирует "адрес" в новую переменную. Следовательно обе переменные теперь имеют возможность делать ссылку на один объект. Эту концепцию легко проиллюстрировать с помощью примера. Рассмотрим класс ExOperators, приведенный ниже: public class EXOperators { internal int р; public EXOperators() {} public static void Main() { ExOperators one = new EXOperators(); one.p = 200; EXOperators two; two = one; two.p = 100; Console.WriteLine(two.p); Console.WriteLine(one.p); } } Пока проигнорируем ключевое слово internalперед переменной р. Оно является модификатором доступа, который будет рассмотрен далее в этой главе. Достаточно сказать, что оно делает переменную рвидимой для метода Main. Приведенный выше пример создает экземпляр объекта EXOperatorsи сохраняет его в локальной переменной one. Эта переменная затем присваивается другой переменной — two. После этого значение pв объекте, на который ссылается two, изменяется на 100. В конце выводится значение переменной pв обоих объектах. Компиляция и выполнение этого даст результат 100 дважды, указывая, что изменение two.рбыло тем же самым, что изменение значения one.р. СравнениеОператоры сравнения обычно совпадают по форме и функциональности в обоих языках. Четырьмя основными операторами являются <— меньше, чем, >— больше, чем, <=— меньше или равно и >=— больше или равно. Чтобы определить, принадлежит ли объект заданному классу или любому из классов предков, Java использует оператор instanceof. Простой пример этого приведен в листинге ниже: String у = "a string"; Object х = у; if (х instanceof String) { System.out.println("х is a string"); } В C# эквивалентом instanceofявляется оператор is. Он возвращает true, если тип времени выполнения заданного класса совместим с указанным типом. Версия C# приведенного выше кода будет иметь следующую форму: string у = "a string"; object х = у; if (х is System.String) { System.Console.WriteLine("x is a string"); } Операторы равенства aрифметические, условные, побитовые, битового дополнения и сдвигаВ обоих языках операторы равенства могут использоваться для тестирования чисел, символов, булевых примитивов, ссылочных переменных. Все другие операторы, упомянутые выше работают таким же образом. Преобразование типовПреобразование в Java состоит из неявного или явного сужения или расширения преобразования типа при использовании оператора (). Можно выполнить аналогичное преобразование типа в C#. C# также вводит ряд действенных способов, встроенных в язык, среди них мы выделим упаковку и распаковку. Так как типы значений являются блоками памяти определенного размера, они прекрасно подходят для целей ускорения. Иногда, однако удобство объектов желательно иметь для типов значений. Упаковка и распаковка предоставляет механизм, который формирует линию соединения между типами значений и ссылочными типами, позволяя преобразовать их в и из объектного типа. Упаковка объекта означает неявное преобразование любого типа значения в объектный тип. Экземпляр объекта создается и выделяется, а значение из типа значения копируется в новый объект. Здесь приведен пример, показывающий, как упаковка работает в C#: // BoxEx.cs public class OverflowEX { public static void Main(String[] args) { int x = 10; Object obj = (Object)x; Console.WriteLine(obj); } } Такой тип функциональности недоступен в Java. Код, представленный ниже не будет компилироваться, так как примитивы не могут преобразовываться в ссылочные типы: // BoxEx.java public class BoxEX { public static void main(String args[]) { int x = 10; object obj = (object)x; System.out.println(obj); } } Распаковка является просто преобразованием объектного типа, приводящим значение снова к соответствующему типу значения. Эта функциональность опять же недоступна в Java. Можно изменить предыдущий код для иллюстрации этой концепции. Сразу заметим, что в то время как упаковка является неявным преобразованием типа, распаковка требует явного преобразования типа. Вот новая реализация BoxEx.cs: // BoxEX.cs public class OverflowEX { public static void Main(String[] args) { int x = 10; Object, obj = (Object)x; Console.WriteLine(obj); int у = (int)obj; Console.WriteLine(y); } } Другим эффективным способом C#, предназначенным для преобразования типов, является возможность определить специальные операторы преобразования. Определенные пользователем преобразования выполняются из типа данных в тип, а не из экземпляра в экземпляр, поэтому они должны быть статическими операциями. Можно использовать ключевое слово impliciteдля объявления определенных пользователем преобразований из одного типа в другой. Предположим, что имеются два класса Manи Car, которые полностью не связаны. Создадим определенное пользователем преобразование, которое переводит один класс в другой. Ниже приведен листинг Man.cs: public class Man { int arms, legs; string name; public Man(){} public int Arms { set { arms = value; } get { return arms; } } public string Name { set { name = value; } get { return name; } } public int Legs { set { legs = value; } get { return legs; } } } Как можно видеть из приведенного примера, класс Manимеет три свойства: можно задать или извлечь Legs, Arms, Name. Ниже представлен листинг класса Car: public class Car { int wheels, doors, headlights; public Car(int wheels, int doors, int headlights) { this.wheels = wheels; this.doors = doors; this.headlights = headlights; } } He существует на самом деле определенных правил о том, что включать в реализацию специального преобразования. Необходимо, однако, сопоставлять как можно больше пар полей данных между двумя операндами. В случае данного примера поле Car.wheelбудет сопоставлено с Man.legs, а поле Car.doorsс Man.arms. Не существует поля в Car, которое представляет что-нибудь похожее на Man.Name, но это не мешает использовать его. Можно, скажем, сопоставить Car.headlightsс длиной строки, которая хранится в Man.name. Любая реализация, которая имеет смысл для программиста, будет приемлема. В этом случае Man.nameне сопоставляется с Car.headlights, вместо этого для headlightsжестко кодируется 2, когда делается преобразование, и полностью отбрасывается Man.name. Следующий код содержит модификацию класса Car: public class Car { int wheels, doors, headlights; public Car(int wheels, int doors, int headlights) { this.wheels = wheels; this.doors = doors; this.headlight = headlights; } public static implicit operator Car(Man man) { return new Car(man.Legs, man.Arms, 2); } public static explicit operator(Car car) { Man man = new Man(); man.Arms = car.doors; man.Legs = car.wheels; man.Name = "john"; return man; } } Мы добавим также переопределенные версии для методов ToString()обоих классов, чтобы вывести содержимое объекта Car. Это делается так: // для Man.cs public override string ToString() { return "[arms:" + arms + "|legs:" + legs + "|name:" + name + "]"; } // для Car.cs public override string ToString() { return "[wheels:" + wheels + "|doors:" + doors + "|headlights:" + headlights + "]"; } Листинг кода ниже показывает использование специального преобразования: // BoxEx.cs public class OverflowEX { public static void Main(String[] args) { Car car = new Car (4, 5, 2); Man man = (Man) car; // использует явное специальное преобразование Console.WriteLine("Man - "); Console.WriteLine(man); Console.WriteLine(); Car car2 = man; // использует неявное специальное преобразование Console.WriteLine("Car - "); Console.WriteLine(car2); } } Компиляция и выполнение этого кода создает показанные ниже результаты: Man - [arms:5|legs:4|name:john] Car - [wheels:4|doors:5|headlights:2] ПерезагрузкаВ начале важно отметить, что перезагрузка операторов не определена в CLS. Однако CLS обращается к ней, потому что языки, обеспечивающие ее функциональность, делают это способом, который могут понять другие языки. Таким образом, языки, которые не поддерживают перезагрузку операторов, все-таки имеют доступ к базовой функциональности. Java является примером языка, который не поддерживает перезагрузку операторов, — ни одна из концепций, рассмотренных в этом разделе, не может ее использовать. Спецификация среды .NET включает ряд рекомендаций для проведения перезагрузки операторов. □ Определите операторы на типах данных значений, которые логически являются встроенным типом языка (таким, как System.Decimal). □ Используйте методы перезагрузки операторов, включающие только тот класс, на котором определены методы. □ Применяйте соглашения об именах и сигнатурах, описанные в CLS. □ Перезагрузка операторов полезна в случаях, где точно известно, каким будет результат операции. □ Предоставьте альтернативные сигнатуры. Не все языки поддерживают вызов перезагруженных операторов. Поэтому постарайтесь всегда включать вторичный метод с подходящим специфическим для домена именем, который имеет эквивалентную функциональность. Перезагрузка операторов связана как с определенными пользователем преобразованиями, так и с типом данных, а не с экземпляром. Это означает, что она связана со всем типом данных, а не с каким-то одним экземпляром объекта, то есть операция всегда должна быть staticи public. В нижеследующем примере создается тип данных значения Wheels, который может выполнять перезагруженное сложение с самим собой. Можно отметить частое использование комментариев и тегов типа XML внутри комментариев, они нужны для документации. Документация C# будет обсуждаться ниже в этом приложении: public struct Wheels { int wheel; // загрузить начальное значение в wheel private Wheels(int initVal); { wheel = initVal; } /// <summary> /// показывает внутреннее число wheels /// </summary> internal int Number { set { wheel = value; } get { return wheel; } } /// <summary> /// возвращает внутреннее число. Если этот метод /// не переопределен, то возвращаемой строкой будет тип Two.Wheels. /// </ summary > /// <returns></returns> public override string ToString() { return wheel.ToString(); } /// < summary> /// выполнить операцию сложения на двух wheels /// </summary> /// <param name="w1"></param> /// <param name="w2"></param> /// <returns></returns> public static Wheels operator + (Wheels w1, Wheels w2) { w1.wheel += w2.wheel; return w1; } /// <summary> /// предоставляет альтернативную функциональность сложения. /// отметим, что вторая альтернатива операции сложения /// находится не в этой структуре, а в классе car /// </summary> /// <param name= "w"></param> /// <returns></returns> public Wheels AddWeels(Wheels w) { this.wheel += w.wheel; return this; } /// <summary> /// поэтому целые литералы можно неявно преобразовать в wheel /// </summary> /// <param name="x"></param> /// <returns></returns> public static implicit operator Wheels(int x) { return new Wheels(x); } } Здесь выделим использование метода AddWheel(), что удовлетворяет рекомендациям об альтернативных сигнатурах. Язык CLS, который не поддерживает перезагрузку операторов, может получить доступ к той же функциональности сложения с помощью этого метода. Фрагмент кода ниже показывает, как может использоваться этот тип данных значения: public static void Main(String[] args) { Wheels front = 2; // неявное преобразование Wheels back = 4; // неявное преобразование Wheels total = front + back; // перезагруженная версия сложения Console.WriteLine(total); } Компиляция и выполнение этого кода дадут в результате 6. Можно также изменить тип Car, чтобы разрешить сложение и вычитание из него Wheels. Следующий код показывает изменения, сделанные в классе Car: public class Car { int wheels, doors, headlights; public Car(int wheels, int doors, int headlights) { this.wheels = wheels; this.doors = doors; this.headlights = headlights; } public Car AddWheel(Two.Wheels w) { this.wheels += w.Number; return this; } internal int Wheels { set { wheels = value; } get { return wheels; } } /// <summary> /// выполняет операцию сложения на Wheel и Car /// </summary> /// <param name="c1">car</param> /// <param name="w1">wheel</param> /// <returns></returns> public static Car operator +(Car c1, Wheels w1) { c1.Wheels += w1.Number; return c1; } /// <summary> /// выполняет операцию вычитания на Wheel и Car /// </summary> /// <param name="c1">car</param> /// <param name="w1">wheel</param> /// <returns></returns> public static Car operator -(Car c1, Wheels w1) { c1.Wheels -= w1.Number; return c1; } public override string ToString() { return "[wheels = " + wheels + "| doors = " + doors + "|" + " headlights = " + headlights + "]"; } } В класс Carтакже был добавлен метод AddWheel. Представленный далее фрагмент кода проверяет функциональность, только что добавленную в Car: public static void Main(String[] args) { Wheels front = 2; Wheels back = 4; Wheels total = front + back; Car greenFordExpedition = new Car(0, 4, 2); Console.WriteLine("initial:\t" + greenFordExpedition); greenFordExpedition += total; Console.WriteLine("after add:\t" + greenFordExpedition); greenFordExpedition -= front; Console.WriteLine("after subtract:\t" + greenFordExpedition); } Компиляция и выполнение этого кода создадут приведенные ниже результаты: initial: CAR-[wheels = 0| doors = 4| headlights = 2 ] after add: CAR-[wheels = 6| doors = 4| headlights = 2 ] after subtract: CAR-[wheels = 4| doors = 4| headlights = 2 ] sizeof и typeofТак как Java не имеет других типов данных значений, кроме примитивных, размер которых всегда известен, то реального применения для оператора sizeofнет. В C# типы данных значений охватывают примитивные типы, а также структуры и перечисления. Конечно, как и в Java, размер примитивных типов известен. Однако необходимо знать, сколько пространства занимает тип structили enum, для чего служит оператор sizeof. Синтаксис достаточно простой: sizeof(<Value Type>), где <Value Type>будет structили enum. Необходимо отметить один момент при использовании оператора sizeof— он может использоваться только в ненадежном контексте. Оператор sizeofне может быть перезагружен. Оператор typeofиспользуется для получения объекта типа экземпляра типа без создания экземпляра типа. В Java каждый тип имеет переменную класса public static, которая возвращает дескриптор объекта Class, ассоциированный с этим классом. Оператор typeofпредоставляет функциональность этого типа. Так же как в случае sizeof, синтаксис будет очень простым: typeof(<Type>), где <Type>является любым типом, определенным пользователем, который вернет объект типа этого типа. ДелегатыДелегаты являются членами пространства имен, которые инкапсулируют ссылку на метод внутри объекта делегата. Объект делегата может затем передаваться в код, вызывающий указанный метод, не зная во время компиляции, какой метод будет вызван. Красоту, мощь и гибкость делегатов можно увидеть только с помощью примера. Давайте посмотрим, как работают делегаты: namespace Samples { using System; using System.Collections; public delegate void TestDelegate(string k); // определяет делегата, // который получает строку в качестве аргумента public class Sample { public Sample() {} public void test(string i) { Console.WriteLine(i + " has been invoked."); } public void text2(string j) { Console.WriteLine("this is another way to invoke {0}" + j); } public static void Main(string[] args) { Sample sm = new Sample(); TestDelegate aDelegate = new TestDelegate(sm.test); TestDelegate anotherDelegate = new TestDelegate(sm.test2); aDelegate("test"); anotherDelegate("test2"); } } } Первый шаг по использованию делегатов состоит в определении делегата. Наш тестовый делегат определяется в строке public delegate void TestDelegate(string k);. Затем определяется класс с методами, которые имеют сигнатуру, аналогичную делегату. Конечный шаг заключается в создании экземпляра делегата который создается так же, как экземпляр класса и реализуется с помощью оператора new. Единственное различие состоит в том, что имя целевого метода передается делегату как аргумент. Затем вызывается делегат. В примере вызывается экземпляр aDelegateс помощью вызова aDelegate("test");. Подробно о классахКак в Java, как и в C#, класс является скелетом, который содержит методы, но не данные. Это структура потенциального объекта. Образование экземпляра класса создает объект на основе этой структуры. Существуют различные ключевые слова и концепции, связанные с классами, которые, были рассмотрены ранее. В этом разделе мы повторим эти ключевые слова и введем новые. МодификаторыКак и в Java, модификаторы в C# используются для модификации объявлений типа и членов. Далее представлен список модификаторов C#. Более подробное определение значений отдельных идентификаторов дано в разделе о ключевых словах данного приложения. Однако некоторые из перечисленных модификаторов являются новыми и будут рассмотрены в ближайших разделах.
КонструкторыПервый метод, который будет вызван в классе в процессе создания экземпляра объекта,— это конструктор. Утверждение справедливо для Java, C++, C# и других языков. Фактически, даже если специально не писать свой собственный конструктор, будет создан конструктор по умолчанию. Но в C# обращение к объекту-предку или другому конструктору обрабатывается совершенно по-другому, чем в Java: public class Parent { } public class Sample: Parent { private string internalVal; private string newVal; public Sample():base() {} public Sample(String s) { internalVal = s; } public Sample(String s, String t) : this(s) { newVal = t; } } Из этого примера видно, что выполнение вызова конструктора предка или даже другого конструктора можно сделать, "расширяя" его с помощью символа " :". В случае конструктора предка используется ключевое слово baseдля идентификации источника, исходящего из объекта предка, в то время как это используется для идентификации источника, исходящего из другого конструктора объекта. Применение подходящей сигнатуры к baseвызовет соответствующий конструктор предка, так же как применение правильной сигнатуры вызовет правильный внутренний конструктор. Мы подчеркнем это, делая некоторые изменения в класс Sample: public class Parent { protected Parent(string a) { Console.WriteLine(a); } protected Parent() { Console.WriteLine("This is the base constructor"); } } public class Sample: Parent { public Sample() { } public Sample(String s):base(s) { } public Sample(String s, String t): this(s) { Console.WriteLine(t); } } C# вводит концепцию деструкторов, позаимствованную из C++. Они работают аналогично завершителям ( finalizer) в Java, их синтаксис, однако, существенно отличается. Деструкторы используют логический знак отрицания ( ~) в качестве префикса для имени класса: ~Sample() { } Рекомендация в отношении кода деструктора: "сборщик мусора" в .NET не вызывается сразу же после того, как переменная покидает область действия. На самом деле имеется некоторый интервал времени или условия памяти, которые инициируют поток выполнения. Бывают случаи, когда деструктор запускается в условиях нехватки памяти, поэтому желательно делать его код как можно короче. Также неплохо вызывать closeна объектах, использующих много ресурсов, прежде чем разрушить контроллеры, которые их применяют. МетодыJava и C# существенно различаются в синтаксисе и идеологии в отношении способа, которым объект образовывает методы. Это связано с одной причиной — не все параметры типа ссылочных данных передаются как ссылки и не все простые типы данных должны передаваться по значению. Имеется возможность передавать аргументы по значению, как параметр in(это способ передачи параметров по умолчанию), по ссылке, как параметр ref, или как параметр out. Следующий код: public static void Main(string[] args) { int a = 10; Console.WriteLine(a); Add(a); Console.WriteLine(a); } public static void Add(int a) { a++; } будет создавать результат, показанный ниже, как в C#, так и в Java: 10 10 Мы передаем апо значению, поэтому это значение не связано со значением в Main. Следовательно, увеличение а в методе Addне влияет на ав Main. Используя возможность, позволяющую передавать простые типы данных как ссылки, приведенный выше код можно изменить следующим образом: public static void Main(string[] args) { int a = 10; Console.WriteLine(a); Add(ref a); Console.WriteLine(a); } public static void Add(ref int a) { a++; } и получить: 10 11 Чтобы использовать ссылочный параметр, надо перед типом параметра использовать ключевое слово ref. В противоположность двум другим типам параметров параметры outне нуждаются в инициализации, перед тем как они передаются в качестве аргументов, они используются для передачи значений назад из метода. Следующий код создаст результат 100: public static void Main(string[] args) { int a; Add(out a); Console.WriteLine(a); } public static void Add(out int a) { a = 100; } Еще одним удачным способом в C# является сокрытие метода. Концепция сокрытия метода обсуждалась ранее в этом приложении. Она позволяет иметь такую же сигнатуру, как и у метода базового класса, не переопределяя базовый метод. Это делается с помощью ключевого слова new, которое помещается перед реализацией метода. Отметим, что, как описано ранее, отсутствие ключевого слова newв экземпляре thisпо прежнему создаст то же поведение и не будет вызывать ошибки компиляции, будет создано только предупреждение. Однако, лучше его использовать, по крайней мере для того, чтобы знать, где сталкиваются сигнатуры этих методов. Вот пример сокрытия метода: namespace Sample { using System; public class SuperHider { public string Test() { return "parent test"; } } public class Hider: SuperHider { public Hider() { } new public string Test() { return "child test"; } } } Следующий листинг показывает, как вызывается любая версия метода Test(): Rider hider = new Hider(); Console.WriteLine(hider.Test()); Console.WriteLine(((SuperHider)h).Test()); Результатом этих вызовов будет: Child test Parent test Сокрытие методов существенно отличается от переопределения методов. В C# переопределение метода является явной процедурой. Это отличается от подхода Java, где переопределение является поведением по умолчанию, когда сигнатура члена суперкласса совпадает с сигнатурой в его подклассе. Чтобы переопределить метод базового класса в C#, необходимо пометить его как virtual. К счастью нельзя просто изменить класс Hider, что показано в данном примере: namespace Samples { using System; public class SuperHider { public string Test() { return "parent test"; } } public class Hider: SuperHider { public Hider() { } public override string Test() { return "child test"; } } } Этот код не будет компилироваться. Надо сначала проинформировать компилятор, что указанный метод, в данном случае SuperHider.test(), может быть переопределен классами потомками. Для этого в C# используется ключевое слово virtual, а методы, к которым применяется этот модификатор, называются виртуальными методами. Возьмем пример подходящего способа выполнения переопределения метода: namespace Samples { using System; public class SuperHider { public virtual string Test() { return "parent test"; } } public class Hider: SuperHider { public Hider() { } public override string Test() { return "child test"; } } } Достоинством переопределения метода является гарантия, что будет вызван самый производный метод. Взгляните на код вызова, представленный ниже, такой же код, что и в примере сокрытия метода, создает два других значения: Hider hider = new Hider(); Console.WriteLine(hider.Test()); Console.WriteLine(((SuperHider)hider).Test()); Так как гарантировано, что всегда вызывается версия testиз Hider, мы знаем, что компиляция и выполнение кода всегда дадут следующие результаты: Child test Child test Единственное синтаксическое различие между абстрактными классами в Java и C# состоит в размещении ключевого слова abstract. Как и в Java, определение абстрактных методов в C# делает класс абстрактным. Свойства и индексаторыРаньше методы get()и set()использовались для доступа к внутренним атрибутам объекта. Сегодня C# вводит концепцию аксессоров ( accessor), которые предоставляют безопасный и гибкий способ получения внутри лих полей, Существует два типа аксессоров. Аксессор get разрешает чтение внутренних полей объекта, а аксессор set позволяет изменять значение внутреннего поля. Ключевое слово valueпредставляет новое значение справа от знака равенства во время присваивания. Отсутствие соответствующего аксессора в объявлении свойства приведет к тому, что свойство будет предназначаться либо только для чтения (нет set), либо только для записи (нет get): namespace Samples { using System; public class Properties { private int age; private string name; public Properties(string name) { this.name = name; } public int Age { get { return age; } set { age = value; } } public string Name { get { return name; } } } } В указанном примере свойство Ageимеет аксессоры getи set, поэтому можно читать или записывать в это свойство. Свойство Name, однако, создается один раз, когда создается новый экземпляр объекта свойств, после чего можно только прочитать значение Name. Свойства доступны, как если бы они были открытыми полями: Properties props = new Properties("john"); props.Age = 21; Console.WriteLine("My name is {0}, and I am {1} years old.", props.Name, props.Age); Результатом этого кода является: My name is john, and I am 21 years old. Примечание. Имена свойств должны быть уникальными. Как предполагает название, индексаторы позволяют легко индексировать атрибуты объектов. Предположим, что нам необходимо предоставить аналогичную функциональность, не создавая двух отдельных свойств. Можно проиндексировать имеющиеся поля, чтобы они были доступны с помощью некоторого ключа (ключ является значением, используемым для доступа к индексу, для массивов ключ является целым значением), или извлечь объявления двух свойств в примере выше и заменить их следующим: public string this[string a] { get { if (a.Equals("Age")) return int.ToString(age); else if (a.Equals("Name")) return name; else { throw new Exception("can only accept 'name' or 'age' key"); } } set { if (a.Equals("Age")) age = int.Parse(value); else { throw new Exception(a + " is read only or does not exist"); } } } Затем можно обратиться к атрибутам свойств следующим образом: Properties props = new Properties("john"); props["Age"] = "21"; Console.WriteLine("my name is {0}, I am {1} years old.", props["Name"], props["Age"]); В результате мы получим: My name is john, I am 21 years old. СобытияСобытия C# предоставляют значительно более надежный и гибкий паттерн наблюдателя, чем в Java. Более того, они могут быть объявлены либо как поля, либо как свойства. Создание события является процессом из трех частей. Сначала мы получаем делегата, а затем событие, связанное с этим делегатом, и наконец, когда происходит некоторое действие, вызывается событие. Проиллюстрируем это, удаляя исключения из класса Propertiesи используя вместо этого события. Наиболее привлекательным свойством событий является гибкость, которая наблюдается при использовании модели делегата. По сути можно использовать любую специальную сигнатуру, которую желательно связать с событием, а затем подписчик на событие должен предоставить целевой метод, который соответствует требуемому списку параметров. Создание этой специальной сигнатуры начинается с определения делегата в коде пространства имен. Для класса Propertiesнеобходимо, чтобы он инициировал строковые события, аналогичные исключениям, которые необходимо порождать: public delegate void ObservableDelegate(string message); Затем объявляется delegateкак поле eventв классе: public event ObservableDelegate ExceptionEventListener; Наконец, переписывается реализация индексатора для активизации приемника события всякий раз, когда возникает условие исключения: public string this[string а] { get { if (a.Equals("Age")) { return int.ToString(age); } else if (a.Equals("Name")) { return name; } else { ExceptionEventListener(can only accept 'name' or 'age' key"); return null; // поток программы продолжается после того, как // событие было инициировано, поэтому необходимо // вернуть значение. В этом случае, так как ключ // является недействительным (не 'name' или 'age'), // возвращается null, указывая отсутствие значения } } set { if (a.Equals("Age") { age = int.Parse(value); } else { listener(a+ " is read only or does not exist"); } } } Экземпляр делегата, связанный с приемником событий, никогда не создается. Это объясняется тем, что создание экземпляра реально происходит на клиенте, который потребляет это событие. Для клиента событие представляется как открытое поле, но не надо думать, что оно не имеет ограничений. Единственными возможными действиями по отношению к полю события являются: □ Создание в событии новых экземпляров делегатов □ Удаление экземпляров делегатов из события C# использует операторы +=и -=соответственно для добавления и удаления экземпляров делегатов из событий. Оба оператора одинаковы в C# и Java. Недопустимо задавать событие, равным одному любому экземпляру делегата. Вместо этого можно добавить в событие столько делегатов, сколько понадобится. Это свободно транслируется в требуемое количество приемников событий для одного события. Пример ниже показывает, как это можно сделать: public delegate void TestEvent(); public class Tester { public event TestEvent testEvent; Tester() { } public void Perform() { testEvent(); } public class Client { Client() { Tester tester = new Tester(); TestEvent a = new TestEvent(Callback1); // сначала создать делегата tester.testEvent += a; // затем добавить его tester.testEvent += new Test(CallBack2); // или можно сделать это // за один проход tester.testEvent += new Test(Callback3); tester.testEvent += new Test(Callback4); tester.Perform(); } public void CallBack1() { // уведомить через e-mail) } public void CallBack2() { // послать факсы } public void CallBack3() { // послать беспроводное сообщение } public void CallBack4() { // сохранить в журнале } } } Как можно понять из приведенного примера, чтобы использовать события класса, необходимо сначала получить метод в классе подписчиков для обработки события (очень похоже на то, как действуют делегаты), затем добавить методы обработки события в событие. Давайте создадим статический метод Notify(): public static void Notify(string i) { Console.WriteLine(i); } Этот метод использует такую же сигнатуру, что и приемник событий класса Properties. В методе Mainможно зарегистрировать метод Notify()и задать условно ошибку, чтобы протестировать событие: Properties props = new Properties("hello"); // зарегистрировать обработчик событий props.ExceptionEventListener += new ExceptionEventListener(test); p["Aged"] = "35"; // неправильный ключ используется // для моделирования ошибки ИсключенияИсключения в C# на первый взгляд являются такими же, как в Java. Инструкции C# try-catchи try-catch-finallyработают подобно своим аналогам в Java (смотрите раздел о ключевых словах). Однако в C# нельзя использовать инструкцию throws, поэтому невозможно указать вызывающей стороне, что некоторый код в методе может порождать исключение. Также имеется try-finally, который не подавляет порожденные исключения, но предлагает блок finally, выполняющий после порождения исключения, чтобы произвести очистку. Порождение исключений делается с помощью инструкции throw. Например, чтобы породить SystemException, используется код throw new SystemException (<arg-list>);. Это полностью совпадает c тем, как исключения порождается в Java. Требуется только инструкция throwsи подходящий класс исключения. Далее представлен список некоторых стандартных классов исключений, предоставляемых средой выполнения .NET. Так же как в Java, их функциональность отражается в данных им именах: □ Exception— базовый класс для всех объектов исключений. □ SystemException— базовый класс для всех ошибок, создаваемых во время выполнения. □ IndexOutOfRangeExceptionвозникает, когда индекс массива во время выполнения оказывается вне границ заданного диапазона. □ NullReferenceExceptionпорождается, когда во время выполнения ссылаются на null. □ InvalidOperationExceptionпорождается некоторыми методами, когда вызов метода бывает не действителен для текущего состояния объекта. □ ArgumentException— базовый класс всех исключений для аргументов. □ ArgumentNullExceptionпорождается если аргумент задан как null, когда это недопустимо. □ InteropExceptionявляется базовым классом для исключений, которые возникают или направлены на среды вне CLR. Одним исключением, которое возникает независимо от того, будет ли оно специально порождаться или нет, является System.OverflowException, связанное с вычисленными результатами, превосходящими диапазон значений типа данных переменной результата. Инструкции checkedи uncheckedмогут инициировать или подавлять связанные с этим исключения. Дополнительная информация о checkedи uncheckedнаходится в разделе данного приложения о ключевых словах. Условная компиляцияПрепроцессор в C# эмулируется. Он выполняется как отдельный процесс, прежде чем компилятор начнет свою работу. Поддерживаемые здесь директивы больше всего соответствуют C++, чем какому-либо другому языку. Конечно, в Java не существует эквивалентов функциональности, описанных в этом разделе. Разрешается определять символы, которые проверяются с помощью простых условных директив. Те, которые оказываются true, включаются и компилируются, иначе код игнорируется. Определение символа может происходить двумя способами. Прежде всего с использованием ключа компилятора /define, за которым следует двоеточие и определяемый символ, например: csc /define:TEST_TEST samples.cs Можно также определить символы на странице конфигурационных свойств проекта, добавляя их в разделенный двоеточиями список констант условной компиляции. Или пойти программным путем, используя директиву #define. В этом случае директива должна появиться раньше, чем что-либо другое, и применяется ко всем лексемам в области действия файла. Здесь перечислены допустимые условные директивы: □ #ifиспользуется для проверки существования символа □ #elifпозволяет добавить несколько ветвей к инструкции #if □ #elseпредоставляет окончательное альтернативное условие для #ifи #elif □ #endifзаканчивает инструкцию #if namespace Samples { using System; #if EXAMPLE public class Example { public Example() { } } #elif TEST_TEST public class Test { public Test() { } } #else public class None { public None() { } } #endif } Добавление инструкции #define TEST_TESTделает класс Testвидимым для компиляции, a #define EXAMPLEделает класс Exampleвидимым для компиляции. Если ничего не добавлять, то будет компилироваться класс None. Для препроцессора C# доступны также две другие директивы — #warningи #error. Они должны помещаться в условное выражение #if. Попробуйте добавить следующую строку под инструкцией #elseв приведенный код: #warning I wouldn't try to instantiate the example object if I were you C# также поддерживает условную функциональность. В коде ниже добавление условного атрибута в AMethod()делает его компилируемым только в том случае, когда определен символ Test: [conditional("TEST_TEST")] public void AMethod() { string s = "I am available only when Test is defined"; } Вопросы безопасностиВ настоящее время код может приходить из разных источников. В Java, до появления Java 2, существовала установка, что все приложения, которым разрешается использовать все свойства языка, должны быть абсолютно надежными. Последующий опыт показал, что такой подход может быть достаточно опасным. Java теперь предоставляет службы политики безопасности с помощью файла java.policy. Приложения подвергаются той же проверке безопасности, что и апплеты. Политика безопасности может редактироваться напрямую или через policytoolдля создания приложений, в которых есть ограничения. Среда .NET для решения этой проблемы использует систему безопасности доступа к коду, которая контролирует доступ к защищенным ресурсам и операциям. Ниже представлен список наиболее важных функций системы безопасности доступа к коду: □ Код может требовать, чтобы вызывающая сторона имела специальные полномочия. □ Выполнение кода ограничено временем выполнения, в этом случае проводятся проверки, которые определяют, соответствуют ли предоставленные полномочия вызывающей стороны требуемым полномочиям операций. □ Код может запрашивать полномочия, которые ему требуются для выполнения и полномочия, которые будут полезны, а также явно утверждать, какие полномочия он никогда не должен иметь. □ Определяются полномочия, которые представляют определенные права для доступа к различным системным ресурсам. □ Администраторы могут выбирать политику системы безопасности, которая присваивает определенные полномочия определенным группам кода. □ Система безопасности доступа к коду предоставляет полномочия, когда компонент загружается. Это предоставление основывается на запросе кода, а также на операциях, разрешенных системой безопасности. Обеспечение политики безопасности делает надежной среду управляемого кода .NET. Это объясняется тем, что каждая загружаемая сборка подчиняется политике безопасности, которая предоставляет полномочия для кода на основе доверия, где доверие базируется на признаках данного кода. Система безопасности .NET позволяет коду использовать защищенные ресурсы, только если он имеет на это "полномочие". Код запрашивает полномочия, которые ему требуются, а политика безопасности, применяемая .NET, определяет, какие полномочия будут реально предоставлены коду. Среда .NET предоставляет в C# классы полномочий доступа к коду, каждый из которых инкапсулирует возможность доступа к определенному ресурсу. Связанный с каждым полномочием класс является перечислением флагов полномочий, используемых для определения конкретного флага полномочий доступа к объекту. Эти полномочия используются для указания .NET, что надо разрешить коду делать и что должно быть разрешено вызывающей этот код стороне. Политика использует эти объекты также для определения, какие полномочия дать коду. Далее следует список стандартных полномочий: □ EnvironmentPermission: определяет полномочия доступа для переменных окружения. Существуют два возможных типа доступа — только для чтения и только для записи. Доступ для записи предоставляет также полномочия для создания и удаления переменных окружения. □ FileIOPermission: существуют три возможных типа полномочий ввода/вывода для файлов — чтение, запись и добавление. Чтение и запись самоочевидны, добавление ограничивается только добавлением, что читать остальное не разрешается. □ ReflectionPermission: управляет возможностью чтения информации о типе неоткрытых членов типа, а также использованием Reflection.Emit. □ RegistryPermission: управляет чтением, записью и созданием в реестре. □ SecurityPermission: управляет совокупностью флагов полномочий, используемых системой безопасности. □ UIPermission: управляет доступом к различным аспектам интерфейса пользователя. □ FileDialogPermission: управляет доступом к файлам на основе диалогового окна системного файла. □ IsolatedStoragePermission: управляет доступом к изолированной памяти. В C# существуют два способа изменения текущих полномочий безопасности с помощью использования: вызовов для классов полномочий в средах .NET или атрибутов полномочий безопасности. ЗаключениеMicrosoft описывает C# как простой, современный, объектно-ориентированный и обеспечивающий безопасность типов язык программирования, производный из С и C++. Так как Java также является модернизацией C++, большая часть синтаксиса и встроенных свойств, представленных в C#, также доступны в Java. C# использует среду .NET и поэтому предлагает встроенный, обеспечивающий безопасность типов, объектно-ориентированный код, взаимодействующий с любым языком, который поддерживает CTS (общую систему типов). Java может работать с С и C++, но без обеспечения безопасности типов. Более того, это достаточно сложно. В то же время C# предоставляет перезагрузку операторов, a Java этого не делает. Имена файлов в C# не связаны с классами в них, как это предусмотрено в Java, а также имена пространств имен не связаны с папками, как имена пакетов в Java. C# вводит концепцию делегатов — указателей функций, которые могут использоваться для инкапсуляции метода с определенной сигнатурой. Также C# предлагает множество встроенных типов данных значений, включая обеспечивающие безопасность типов перечисления, структуры и встроенные примитивы, которые представляют достойную альтернативу примитивам Java. В C# реализовано двунаправленное преобразование между ссылками и типами данных значений, называемое упаковкой и распаковкой. Эта функциональность не поддерживается в Java. C# поддерживает использование классов, укомплектованных полями, конструкторами и методами в качестве шаблонов для описания типов данных, и предоставляет возможность определить деструкторы — методы, вызываемые перед тем, как класс попадает к сборщику мусора. C# предоставляет также три подхода к параметрам методов — in, outили ref, где по умолчанию используется in. C# вводит также концепцию сокрытия методов, а также поддержку явного переопределения с помощью ключевых слов virtualи override. В C# предусмотрены свойства как альтернатива методам getXXX()и setXXX(), обеспечивающие способ безопасного доступа к внутренним полям. Кроме того, C# допускает создание индексаторов для предоставления индексного доступа к внутренним полям объекта. В отличие от Java, однако, в C# нет возможности объявить, что метод может порождать исключение. Пространства имен C# предоставляют значительно более гибкий способ группирования связанных классов. В C# возможна эмуляция препроцессора, код условно включается или исключается на основе существования определенных символов. C# предоставляет модель безопасности на основе полномочий, которой можно управлять программным путем. |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|