Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Структуры - это типы значений↑ ⇐ ПредыдущаяСтр 4 из 4 Содержание книги Поиск на нашем сайте
Хотя структуры относятся к типам значений, очень часто их можно трактовать так же, как классы. Например, имея определение класса Dimensions из предыдущего раздела, можно написать следующий код: Dimensions point = new Dimensions(); point.Length = 3; point.Width = 6; Следует обратить внимание, что поскольку структуры являются типами значений, операция new с ними работает несколько иначе, чем с классами и другими ссылочными типами. Вместо того чтобы выделять память в куче, операция new просто вызывает подходящий конструктор, инициализируя поля в соответствии с переданными ему параметрами. На самом деле, для структур можно написать такой код: Dimensions point; point.Length = 3; point.Width = 6; Если бы Dimensions был классом, это привело бы к ошибке компиляции, потому что point содержал бы неинициализированную ссылку - т.е. адрес, ни на.что не указывающий, а потому вы не могли бы присваивать значения его полям. Для структур же объявление переменной на самом деле выделяет пространство в стеке для полной структуры, поэтому она тут же готова к присваиванию значений полям. Однако следует отметить, что показанный ниже код вызовет ошибку компиляции с сообщением о том, что используется неинициализированная переменная: Dimensions point; Double D = point.Length; Структуры следуют тем же правилам, что и все другие типы данных: перед использованием все должно быть инициализировано. Структура рассматривается как полностью инициализированная, когда для ее создания была вызвана операция new или же когда всем ее полям индивидуально были присвоены значения. И, конечно же, структура, определенная как поле класса, инициализируется автоматическим обнулением при инициализации ее включающего объекта. Тот факт, что структуры - это типы значений, оказывает влияние на производительность. В зависимости от того, как используются структуры, это может быть и плохо, и хорошо. Положительным моментом является то, что выделение памяти для структур происходит очень быстро, потому что они встроены или размещаются в стеке. То же самое касается удаления структур при выходе из контекста. С другой стороны, всякий раз, когда вы передаете структуру в виде параметра или присваиваете одну структуру другой (как в А=В, когда А и В - структуры), происходит копирование всего содержимого структуры, в то время как в случае с классами копируется только ссылка. Это приводит к потерям производительности, которые зависят от размера структуры - это подчеркивает тот факт, что структуры на самом деле предназначены для хранения небольших порций данных. Тем не менее, когда структура передается методу в виде параметра, этих потерь производительности можно избежать, применяя ref-параметры - при этом передается только адрес в памяти, где находится структура. Это так же быстро, как и передача объекта класса. С другой стороны, если вы так поступаете, то должны помнить, что вызываемый метод может в принципе изменить значение переданной структуры. Структуры и наследование Структуры не предназначены для наследования. То есть наследовать от структуры нельзя. Единственное исключение состоит в том, что структуры, как и все другие типы в С#, в конечном итоге наследуются от класса System.Object. Поэтому структуры имеют доступ к методам класса System.Object и даже могут переопределять их - очевидным примером может служить переопределение метода ToString(). Действительная цепочка наследования, которая приводит к структуре, состоит в том, что каждая структура наследуется от класса System.ValueType, который, в свою очередь, наследуется от System.Object.ValueType, который не добавляет новых членов к Object, но предоставляет реализацию ряда из них более подходящим для структур способом. Следует отметить, что выбрать произвольный базовый класс для структуры невозможно: все они наследуются от ValueType. Конструкторы структур Объявлять конструкторы структур можно точно так же, как это делается для классов, за исключением того, что не разрешено определять конструкторы без параметров. Это может показаться бессмысленным, но причина кроется в реализации исполняющей среды.NET. Бывают некоторые редкие случаи, когда.NET не в состоянии вызвать определенный вами конструктор без параметров. В Microsoft двинулись по пути наименьшего сопротивления, просто запретив пользовательские конструкторы без параметров для структур в С#. Таким образом, конструктор по умолчанию, который инициализирует все поля нулевыми значениями, всегда присутствует неявно, даже если применяются другие конструкторы с параметрами. Также невозможно обойти конструктор по умолчанию, определяя начальные значения полей. Следующий код вызовет ошибку компиляции: struct Dimensions { public double Length = 1; // Ошибка. Начальные значения не разрешены, public double Width = 2; // Ошибка. Начальные значения не разрешены. } Конечно, если бы Dimensions была объявлена классом, этот код скомпилировался бы без проблем. Аналогично, структуру можно снабдить методом Close() или Dispose() - так же, как это делается с классами. Частичные классы Ключевое слово partial (частичный) позволяет определить класс, структуру или интерфейс, распределенный по нескольким файлам. Однако в ситуации, когда множеству разработчиков требуется доступ к одному и тому же классу, или же (что более вероятно) в ситуации, когда некоторый генератор кода генерирует часть класса, такое разделение класса на несколько файлов может оказаться полезным. Ключевое слово partial просто помещается перед классом, структурой или интерфейсом. В следующем примере класс TheBigClass размещается в двух отдельных файлах: BigClassPartl.cs и BigClassPart2.cs. // BigClassPartl.cs partial class TheBigClass { public void MethodOne() { } } // BigClassPart2.cs partial class TheBigClass { public void MethodTwo() { } } Когда компилируется проект, частью которого являются эти два файла, создается единый тип TheBigClass с двумя методами - MethodOne() и MethodTwo(). Если в описании класса используется любое из перечисленных ниже ключевых слов, оно должно быть повторено во всех его частях, находящихся в разных файлах: - public - private - protected - internal - abstract - sealed - new - общие ограничения Вложенные части также допускаются, до тех пор, пока ключевое слово partial предшествует ключевому слову class во вложенном типе. Атрибуты, XML-комментарии, интерфейсы, параметры обобщенных типов и члены всех частей частичного класса компилируются в единый тип. Предположим, что есть два исходных файла: // BigClassPartl.cs [CustomAttribute] partial class TheBigClass: TheBigBaseClass, IBigClass { public void MethodOne() { } } // BigClassPart2.cs [AnotherAttribute] partial class TheBigClass: IotherBigClass { public void MethodTwoO { } } После компиляции эквивалентный исходный файл получится таким:
[CustomAttribute] [AnotherAttribute] partial class TheBigClass:TheBigBaseClass,IBigClass,OtherBigClass { public void MethodOne() { } public void MethodTwo() { } } Статические классы Ранее в этой главе обсуждались статические конструкторы и то, как они позволяют инициализировать статические переменные-члены. Если класс не содержит ничего кроме статических методов и свойств, этот класс сам по себе может стать статическим. Статический класс функционально представляет собой то же самое, что и класс с приватным статическим конструктором. Создать экземпляр такого класса невозможно. Если указать ключевое слово static в объявлении класса, компилятор будет гарантировать, что к этому классу никогда не будут добавлены нестатические члены. В противном случае будет выдана ошибка компиляции. Это гарантирует также, что экземпляры этого класса никогда не будут созданы. Синтаксис объявления статического класса выглядит следующим образом: static class StaticUtilities { public static void HelperMethod() { } } Объект типа StaticUtilities не нужен для вызова HelperMethod(). При вызове указывается имя типа: StaticUtilities.HelperMethod();
Класс Object Как отмечалось ранее, классы.NET изначально унаследованы от System.Object. Фактически, если при определении нового класса базовый класс не указан, компилятор автоматически предполагает, что он наследуется от Object. Поскольку в этой главе наследование не используется, все классы, которые вы видели до сих пор, на самом деле унаследованы от System.Object. (Как упоминалось выше, для структур это наследование непрямое. Структуры всегда наследуются от System.ValueType, который, в свою очередь, унаследован от System.Object.) Практическое значение этого в том, что помимо методов и свойств, которые вы определяете, также появляется доступ к множеству общедоступных и защищенных методов-членов, которые определены в классе Object. Эти методы присутствуют во всех определяемых классах. Методы System.Object На данный момент следующий список кратко резюмирует назначение каждого метода, за исключением ToString(), который описан более подробно. - ToString(). Этот метод предназначен для выдачи базового, быстрого и простого строкового представления. Применяйте его, когда нужно получить представление о содержимом объекта - возможно, в целях отладки. Он предлагает очень ограниченные средства форматирования данных. Например, даты в принципе могут быть отображены в огромном разнообразии форматов, но DateTime.ToString() не оставляет никакого выбора в этом отношении. Если нужно более сложное строковое представление, которое, например, принимает во внимание установленные предпочтения или местные стандарты, то понадобится реализовать интерфейс IFormattable (см. раздел 6.9). - GetHashCode(). Этот метод используется, когда объект помещается в структуру данных, известную как карта (map), которая также называется хеш-таблицей или словарем. Применяется классами, которые манипулируют этими структурами, чтобы определить, куда именно в структуру должен быть помещен объект. Если вы намерены использовать свой класс как ключ словаря, то должны переопределить GetHashCode(). Существуют достаточно строгие требования относительно того, как нужно реализовывать перегрузку, и вы ознакомитесь с ними, когда будете изучать словари в разделе 6.10. - Equals() (обе версии) и ReferenceEquals(). Как несложно догадаться, учитывая существование трех различных методов сравнения объектов, среда.NET использует довольно сложную схему определения эквивалентности объектов. Следует учитывать и использовать тонкие различия между этими тремя методами и операцией сравнения ==. Кроме того, также существуют ограничения, регламентирующие, как следует переопределять виртуальную версию Equals() с одним параметром, если вы решитесь на это - поскольку некоторые базовые классы из пространства имен System.Collections вызывают этот метод и ожидают от него определенного поведения. Мы вернемся к этим методам в разделе 6.7, когда будем рассматривать операции. - Finalize(). Этот метод будет описан в разделе 6.13. Его назначение в С# примерно соответствует деструкторам С++, и он вызывается при сборке' мусора для очистки ресурсов, занятых ссылочным объектом. Реализация Finalize() из Object на самом деле ничего не делает и игнорируется сборщиком мусора. Обычно переопределять Finalize() необходимо, если объект владеет неуправляемыми ресурсами, которые нужно освободить при его уничтожении. Сборщик мусора не может сделать это напрямую, потому что он знает только об управляемых ресурсах, поэтому полагается на финализацию, определенную вами. - GetType(). Этот метод возвращает экземпляр класса, унаследованный от System.Туре. Этот объект может предоставить большой объем информации о классе, членом которого является ваш объект, включая базовый тип, методы, свойства и т.п. System.Туре также представляет собой стартовую точку технологии рефлексии.NET. Этой теме посвящен раздел 6.14. - MemberwiseClone(). Это единственный член System.Object, который в книге подробно нигде больше не упоминается. Вообще говоря, в этом нет необходимости, поскольку его концепция весьма проста. Этот метод просто создает копию объекта и возвращает ссылку на эту копию (а в случае типа значения - ссылку на упаковку). Отметим, что при этом выполняется неглубокое копирование, т.е. копируются все типы значений в классе. Если же класс включает в себя члены ссылочных типов, то копируются только ссылки, а не объекты, на которые они указывают. Этот метод является защищенным, а потому не может вызываться для копирования внешних объектов. К тому же он не виртуальный, а потому переопределять его реализацию нельзя. Метод ToString() Вы уже встречались с методом ToString() в разделе 6.2. Этот метод предоставляет наиболее удобный способ быстрого получения строкового представления объекта. Например: int i = -50; string str = i.ToString(); // возвращает "-50" Вот другой пример:
enum Colors {Red, Orange, Yellow}; // далее в коде... Colors favoriteColor = Colors.Orange; string str = favoriteColor.ToString(); // возвращает "Orange" Метод Object.ToString() объявлен как виртуальный, и во всех этих примерах используется преимущество того факта, что его реализация для встроенных типов С# переопределена с целью возврата корректного представления этих типов. Не следует думать, что перечисление Colors считается предопределенным типом данных. На самом деле оно реализовано как структура, унаследованная от System.Enum, а в System.Enum предлагается достаточно интеллектуальная реализация ToString(), имеющая дело со всеми типами перечислений, которые вы определяете. Если вы не переопределяете ToString() в своих классах, то они просто наследуют реализацию этого метода от System.Object, которая просто отображает имя класса. Если же необходимо, чтобы метод ToString() возвращал информацию о значении объекта вашего класса, то должны переопределить его. Чтобы проиллюстрировать это, в следующем примере - Money - определен очень простой класс, также названный Money, который представляет суммы в валюте США. Класс Money служит оболочкой типа decimal, но включает метод ToString(). Обратите внимание, что он должен быть объявлен как override, потому что переопределяет метод, предоставленный Object. Переопределение рассматривается более подробно в разделе 6.4. Полный код примера приведен ниже. Обратите внимание, что он также иллюстрирует применение свойств для помещения полей в оболочки. using System; namespace Wrox { class MainEntryPoint { static void Main(string[] args) { Money cashl = new Money(); cashl.Amount = 40M; Console.WriteLine("cashl.ToString() возвращает: " + cashl.ToString()); Console.ReadLine(); } } class Money { private decimal amount; public decimal Amount { get { return amount; } set { amount = value; } } public override string ToString() { return "$'* + Amount.ToString(); } } } Этот пример просто иллюстрирует синтаксические свойства С#. В С# уже имеется предопределенный тип для представления денежных сумм - decimal, поэтому в реальности писать класс, дублирующий эту функциональность, не придется, если только вы не пожелаете добавить к нему какие-то другие методы. Во многих случаях, для удовлетворения требований форматирования, при отображении в строке денежной суммы, скорее всего, будет использоваться метод String.Format() (он описан в разделе 6.8). В методе Main() сначала создается экземпляр объекта Money, а затем - BetterMoney. После этого вызывается метод ToString() (который в действительности выполняет переопределенную версию этого метода. Запуск показанного кода даст следующий результат: cash1.ToString() возвращает: $40 Расширяющие методы Существует множество способов расширить класс. При наличии исходного кода класса наследование, описанное в разделе 6.4, будет отличным способом добавления функциональности к объектам. Но что, если исходный код недоступен? Расширяющие методы позволяют изменять класс, не имея его исходного кода. Расширяющие методы (extension method) - это статические методы, которые могут стать частью класса, когда исходный код этого класса не доступен. Предположим, что классу Money из предыдущего примера понадобился метод AddToAmount (decimal amountToAdd). Однако по какой-то причине первоначальный исходный код сборки не может быть изменен непосредственно. Все, что остается - это создать статический класс и добавить в него статический метод AddToAmount. Вот как должен выглядеть этот код: namespace Wrox { public static class MoneyExtension { public static void AddToAmount(this Money money, decimal amountToAdd) { money.Amount += amountToAdd; } } } Следует обратить внимание на параметры метода AddToAmount. Для расширяющего метода первым параметром всегда должен быть тип, который подлежит расширению, предваренный ключевым словом this. Это сообщит компилятору, что данный метод должен стать частью типа Money. В приведенном примере Money является расширяемым типом. В расширяющем методе можно обращаться к общедоступным методам и свойствам расширяемого типа. В главной программе метод AddToAmount выглядит как еще один метод типа Money. Первый параметр не появляется, и ничего делать с ним не нужно. Новый метод вызывается подобно любому другому методу: сash1.AddToAmount (10М); Несмотря на то что расширяющий метод является статическим, можно использовать стандартный синтаксис метода экземпляра. Обратите внимание, что мы вызываем AddToAmount на переменной-экземпляре cash1, не указывая имя типа. Если имя расширяющего метода совпадет с именем уже существующего метода класса, то расширяющий метод никогда не будет вызван. Любые методы экземпляра, имеющиеся в классе, имеют преимущество. Итоги В настоящей главе был представлен синтаксис создания и манипулирования объектами в С#. Вы увидели, как объявлять статические члены и члены уровня экземпляров, включая поля, свойства, методы и конструкторы. Кроме того, было показано, что в С# добавляется ряд новых средств, не предусмотренных в классической модели объектно-ориентированного программирования и в некоторых других языках: статические конструкторы, предоставляющие возможность инициализации статических полей, а также структуры, которые позволяют определять высокопроизводительные типы, хоть и предлагающие более ограниченный набор средств, зато не требующие использования управляемой кучи. Вы также узнали о том, что все типы С# изначально наследуются от System.Object, а это означает, что все типы имеют базовый набор полезных методов, включая ToString().
|
||||
Последнее изменение этой страницы: 2016-12-27; просмотров: 254; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.118.162.8 (0.014 с.) |