Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Структуры - это типы значений

Поиск

Хотя структуры относятся к типам значений, очень часто их можно трактовать так же, как классы. Например, имея определение класса 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 с.)