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



ЗНАЕТЕ ЛИ ВЫ?

Передача параметров в методы

Поиск

Объекты и типы

Отличия между структурами и классами

Члены классов

Передача по значению и передача по ссылке

Перегрузка методов

Конструкторы и статические конструкторы

Поля, доступные только для чтения

Частичные классы

Статические классы

Класс Object, от которого наследуются все другие типы

 

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

Обратите внимание, что наследование и все, что с Ним связано, будет рассмотрено в разделе 6.4.

В данной главе представлен базовый синтаксис, ассоциированный с классами. При этом предполагается, что вы уже знакомы с принципами, лежащими в основе понятия классов - например, знаете, что такое конструктор или свой­ство. Раздел в большей степени посвящена применению этих понятий в коде С#.

Классы и структуры

Классы и структуры - это, по сути, шаблоны, по которым можно создавать объекты. Каждый объект содержит данные и методы, манипулирующие этими Данными. Класс опре­деляет, какие данные и функциональность может иметь каждый конкретный объект (назы­ваемый экземпляром) этого класса. Например, если имеется класс, представляющий заказ­чика, он может определять такие поля, как CustomerlD, FirstName, LastName и Address, которые нужны ему для хранения информации о конкретном заказчике. Он также может определять функциональность, которая работает с данными, хранящимися в этих полях. Вы создаете экземпляр этого класса для представления конкретного заказчика, устанавли­ваете значения полей экземпляра и используете его функциональность.

class PhoneCustomer

{

public const string DayOfSendingBill = "Monday";

public int CustomerlD;

public string EirstName;

public string LastName;

}

Структуры отличаются от классов тем, как они сохраняются в памяти и как к ним осуществляется доступ (классы - это ссылочные типы, размещаемые в куче, структуры - типы значений, размещаемые в стеке), а также некоторыми свойствами (например, струк­туры не поддерживают наследование). Из соображений производительности вы будете использовать структуры для небольших типов данных. Однако в отношении синтаксиса структуры очень похожи на классы. Главное отличие состоит в том, что при их объявле­нии используется ключевое слово struct вместо class. Например, если вы хотите, чтобы все экземпляры PhoneCustomer размещались в стеке, а не в управляемой куче, то можете написать следующий код:

struct PhoneCustomerStruct

{

public const string DayOfSendingBill = "Monday";

public int CustomerlD;

pubcli string FirstName;

public string LastName;

}

При создании как классов, так и структур используется ключевое слово new для объявления экземпляра. В результате объект создается и инициализируется. В следующем приме­ре по умолчанию все поля обнуляются:

PhoneCustomer myCustomer = new PhoneCustomer();

// работает с классом

PhoneCustomerStruct myCustomer2 = new PhoneCustomerStruct();

// работает со структурой

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

Классы

Данные и функции, объявленные внутри класса, известны как члены класса. В официаль­ной терминологии Microsoft делается различие между данными-членами и функциями-чле­нами. В дополнение к членам, классы могут содержать в себе вложенные типы (такие как другие классы). Доступность членов класса может быть описана как public, protected, internal protected, private или internal. Эти вопросы детально рассматриваются в разделе 6.5.

Данные-члены

Данные-члены - это те члены, которые содержат данные класса - поля, константы, события. Данные-члены могут быть статическими (static). Член-класса является членом экземпляра, если только он не объявлен явно как static.

Поля (field) - это любые переменные, ассоциированные с классом. Вы уже видели поля в классе PhoneCustomer в предыдущем примере.

После создания экземпляра объекта PhoneCustomer к его полям можно обращаться с использованием синтаксиса Объект.ИмяПоля, как показано в следующем примере:

PhoneCustomer Customer1 = new PhoneCustomer ();

Customer1.FirstName = "Simon";

Константы могут быть ассоциированы с классом тем же способом, что и перемен­ные. Константа объявляется с помощью ключевого слова const. Если она объявлена как public, то в этом случае становится доступной извне класса.

class PhoneCustomer

{

public const string DayOfSendingBill = "Monday";

public int CustomerlD; public string FirstName;

public string LastName;

}

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

Функции-члены

Функции-члены - это члены, которые обеспечивают некоторую функциональность для манипулирования данными класса. Они включают методы, свойства, конструкторы, фина-лизаторы, операции и индексаторы.

· Методы (method) - это функции, ассоциированные с определенным классом. Как и данные-члены, по умолчанию они являются членами экземпляра. Они могут быть объявлены статическими с помощью модификатора static.

· Свойства (property) - это наборы функций, которые могут быть доступны клиенту таким же способом, как общедоступные поля класса. В С# предусмотрен специаль­ный синтаксис для реализации чтения и записи свойств для классов, поэтому пи­сать собственные методы с именами, начинающимися на set и get, не понадобит­ся. Поскольку не существует какого-то отдельного синтаксиса для свойств, который отличал бы их от нормальных функций, создается иллюзия объектов как реальных сущностей, предоставляемых клиентскому коду.

· Конструкторы (constructor) - это специальные функции, вызываемые автоматиче­ски при инициализации объекта. Их имена совпадают с именами классов, которым они принадлежат, и они не имеют типа возврата. Конструкторы полезны для ини­циализации полей класса.

· Финализаторы (finalizer) похожи на конструкторы, но вызываются, когда среда CLR определяет, что объект больше не нужен. Они имеют то же имя, что и класс, но с предшествующим символом тильды (~). Предсказать точно, когда будет вызван фина-лизатор, невозможно. Финализаторы рассматриваются в разделе 6.13.

· Операции (operator) - это простейшие действия вроде + или -. Когда вы складывае­те два целых числа, то, строго говоря, применяете операцию + к целым. Однако С# позволяет указать, как существующие операции будут работать с пользовательскими классами (так называемая перегрузка операций). В разделе 6.7 операции рассматриваются более подробно.

· Индексаторы (indexer) позволяют индексировать объекты таким же способом, как массив или коллекцию.

Методы

Следует отметить, что официальная терминология С# делает различие между функция­ми и методами. Согласно этой терминологии, понятие "функция-член" включает не только методы, но также другие члены, не являющиеся данными, класса или структуры. Сюда вхо­дят индексаторы, операции, конструкторы, деструкторы, а также - возможно, несколько неожиданно - свойства. Они контрастируют с данными-членами: полями, константами и событиями.

Объявление методов

В С# определение метода состоит из любых модификаторов (таких как спецификация доступности), типа возвращаемого значения, за которым следует имя метода, затем списка аргументов в круглых скобках и далее - тела метода в фигурных скобках:

[модификаторы] тип_возврата ИмяМетода([параметры])

{

// Тело метода

}

Каждый параметр состоит из имени типа параметра и имени, по которому к нему мож­но обратиться в теле метода. Вдобавок, если метод возвращает значение, то для указания точки выхода должен использоваться оператор возврата вместе с возвращаемым значени­ем. Например:

public bool IsSquare(Rectangle rect)

{

return (rect.Height == rect.Width);

}

В этом коде применяется один из базовых классов.NET, System.Drawing.Rectangle, представляющий прямоугольники.

Если метод не возвращает ничего, то в качестве типа возврата указывается void, по­скольку вообще опустить тип возврата невозможно. Если же он не принимает аргументов, то все равно после имени метода должны присутствовать пустые круглые скобки. При этом включать в тело метода оператор возврата не обязательно - метод возвращает управление автоматически по достижении закрывающей фигурной скобки. Следует отметить, что ме­тод может содержать любое необходимое количество операторов возврата:

public bool IsPositive(int value);

{

if (value < 0)

return false;

return true;

}

Вызов методов

В следующем примере, MathTest, демонстрируется определение и создание экземпля­ров классов, а также определение и вызов методов. Помимо класса, содержащего метод Main(), в нем определен класс по имени MathTest, содержащий набор методов и полей.

MathTest.cs

using System;

namespace Wrox

{

class MainEntryPoint

{

static void Main()

{

// Try calling some static functions

Console.WriteLine("Pi is " + MathTest.GetPi());

int x = MathTest.GetSquareOf(5);

Console.WriteLine("Square of 5 is " + x);

 

// Instantiate at MathTest object

MathTest math = new MathTest(); // this is C#'s way of

// instantiating a reference type

 

// Call non-static methods

math.Value = 30;

Console.WriteLine(

"Value field of math variable contains " + math.Value);

Console.WriteLine("Square of 30 is " + math.GetSquare());

}

}

 

// Define a class named MathTest on which we will call a method

class MathTest

{

public int Value;

 

public int GetSquare()

{

return Value*Value;

}

public static int GetSquareOf(int x)

{

return x*x;

}

public static double GetPi()

{

return 3.14159;

}

}

}

Запуск на выполнение примера MathTest даст следующий результат:

Pi равно 3.14159

5 в квадрате равно 25

Поле value переменной math содержит 30

30 в квадрате равно 900

Как можно видеть в коде, класс MathTest содержит поле, которое хранит число, а так­же метод для вычисления квадрата этого числа. Кроме того, он включает два статических метода - один для возврата значения числа "пи" и другой - для вычисления квадрата чис­ла, переданного в параметре.

Некоторые свойства этого класса на самом деле не могут служить примером правиль­ного дизайна программы С#. Например, GetPi() следовало бы реализовать в виде кон­стантного поля, но остальная часть демонстрирует концепции, которые пока не рассмат­ривались.

ParameterTest.cs

using System;

namespace Wrox

{

class ParameterTest

{

static void SomeFunction(int[] ints, int i)

{

ints[0] = 100;

i = 100;

}

public static int Main()

{

int i = 0;

int[] ints = { 0, 1, 2, 4, 8 };

// Display the original values

Console.WriteLine("i = " + i);

Console.WriteLine("ints[0] = " + ints[0]);

Console.WriteLine("Calling SomeFunction...");

// After this method returns, ints will be changed,

// but i will not

SomeFunction(ints, i);

Console.WriteLine("i = " + i);

Console.WriteLine("ints[0] = " + ints[0]);

return 0;

}

}

}

Ниже показан вывод этой программы:

ParameterTest.exe i = 0

ints[0) = 0

Вызов SomeFunction...

i = 0

ints[0] = 100

Обратите внимание, что значение i осталось неизменным, но измененные значения в ints также изменились в исходном массиве.

Поведение строк также отличается. Дело в том, что строки являются неизменными (из­менение значения строки то приводит к созданию совершенно новой строки), поэтому строки не демонстрируют поведение, характерное для ссылочных типов. Любые измене­ния, проведенные в строке внутри метода, не влияют на исходную строку. Этот момент более подробно обсуждается в разделе 6.9.

Параметры ref

Как уже упоминалось, по умолчанию параметры передаются по значению. Тем не менее, можно принудительно передавать значения по ссылке, для чего используется ключевое слово ref. Если параметр передается в метод, и входной аргумент этого метода снабжен префиксом ref, то любые изменения этой переменной, которые сделает метод, отразятся на исходном объекте:

static void SomeFunction (int [ ] ints, ref int i)

{

ints[0] = 100;

i = 100;

// изменение i сохранится после завершения SomeFunction ()

}

Ключевое слово ref также должно быть указано при вызове метода:

SomeFunction(ints, ref i);

И, наконец, также важно понимать, что С# распространяет требование инициализации на параметры, переданные методу. Любая переменная должна быть инициализирована прежде, чем она будет передана в метод, независимо от того, передается она по значению или ссылке.

Параметры out

В С-подобных языках функции часто возвращают более одного значения. Это обеспе­чивается применением выходных параметров, за счет присваивания значений перемен­ным, переданным в метод по ссылке. Часто первоначальное значение таких переменных не важно. Эти значения перезаписываются в функции, которая может даже не обращать внимания на то, что в них хранилось первоначально.

Было бы удобно использовать то же соглашение в С#. Однако в С# требуется, чтобы переменные были инициализированы каким-то начальным значением перед тем, как к ним будет выполнено обращение. Хотя можно инициализировать входные переменные какими-то бессмысленными значениями до передачи их в функцию, которая наполнит их осмысленными значениями, этот прием выглядит в лучшем случае излишним, а в худшем - сбивающим с толку. Тем не менее, существует способ обойти требование компилятора С# относительно начальной инициализации переменных.

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

static void SomeFunction (out int i)

{

i = 100;

}

public static int Main()

{

int i; // переменная i объявлена, но не инициализирована

SomeFunction(out i);

Console.WriteLine(i);

return 0;

}

Именованные аргументы

Обычно параметры должны передаваться в метод в том же порядке, в котором они оп­ределены. Именованные аргументы позволяют передавать параметры в любом порядке. То есть для приведенного ниже метода:

string FullName(string firstName, string lastName)

{

return firstName + " " + lastName;

}

возможны следующие вызовы, которые вернут одно и то же полное имя:

FullName("John", "Doe");

FullName(lastName: "Doe", firstName: "John");

Если метод имеет несколько параметров, в одном вызове можно смешивать позицион­ные и именованные параметры.

Необязательные аргументы

Параметры также могут быть необязательными. Для таких параметров должны опреде­ляться значения по умолчанию. Необязательные параметры должны располагаться послед­ними в списке. Поэтому следующее объявление метода будет неправильным:

void TestMethod(int optionalNumber = 10, int notOptionalNumber)

{

System.Console.Write(optionalNumber + notOptionalNumber);

}

Чтобы этот метод работал, параметр optionalNumber должен быть определен послед­ним.

Перегрузка методов

В С# поддерживается перегрузка методов - может существовать несколько версий ме­тода с разными сигнатурами (т.е. именем, количеством и/или типами параметров). Чтобы перегрузить методы, вы просто объявляете методы с одинаковыми именами, но разным с разным количеством или типами параметров:

class ResultDisplayer

{

void DisplayResult(string result)

{

// реализация

}

void DisplayResult(int result)

{

// реализация

}

}

Если вас не устраивают необязательные параметры, для достижения того же эффекта можно применить перегрузку методов:

class MyClass

{

int DoSomething(int x)

// нужен 2-й параметр со значением по умолчанию 10

{

DoSomething(х, 10);

}

int DoSomething(int x, int y)

{

// реализация

}

}

Как в любом другом языке, неправильная перегрузка методов чревата трудноуловимы­ми ошибками времени выполнения. В разделе 6.4 обсуждаются способы предотвращения этой проблемы при кодировании. А пока вы должны знать, что С# обеспечивает некоторую ми­нимальную защиту относительно параметров перегруженных методов:

- недостаточно, чтобы два метода отличались только типом возвращаемого значения;

- недостаточно, чтобы два метода отличались только спецификацией параметров ref и out.

Свойства

Идея свойства заключается в методе или паре методов, которые ведут себя с точки зре­ния клиентского кода как поле. Хороший пример этого - свойство Height (высота) класса Windows Forms. Предположим, имеется следующий код:

// mainForm относится к типу System.Windows.Forms

mainForm.Height = 400;

При выполнении этого кода высота окна будет установлена в 400, и вы сразу увидите изменение размеров окна на экране. Синтаксически это выглядит как установка значения поля, но на самом деле подобным образом вызывается средство доступа (accessor), которое содержит в себе код, изменяющий размер формы.

Для определения свойства на С# используется следующий синтаксис:

public string SomeProperty

{

get

{

return "Это значение свойства";

}

set

{

// Сделать все необходимое для установки свойства

}

}

Средство доступа get не принимает никаких параметров и должно возвращать значе­ние того типа, который объявлен для свойства. Для средства доступа set не должны ука­зываться явные параметры, но компилятор предполагает, что оно принимает один пара­метр по имени value, относящийся к тому же типу. В качестве примера следующий код содержит свойство по имени Age, которое устанавливает поле age. В данном примере age служит переменной заднего плана для свойства Age.

private int age;

public int Age

{

get f

{

return age;

}

 

set

{

age = value;

}

}

Обратите внимание на применяемые здесь соглашения об именовании. Здесь использу­ется преимущество зависимости от регистра С#, благодаря которому общедоступное свой­ство именуется в стиле Pascal casing, а для эквивалентного приватного поля, если таковое присутствует, применяется стиль именования Camel casing. Некоторые разработчики пред­почитают использовать имена полей с префиксом - символом подчеркивания: _age. Это обеспечивает чрезвычайно согласованный способ идентификации полей.

Замечание о встраивании

Некоторые разработчики могут быть обеспокоены тем, что в предыдущих разделах де­монстрируется множество ситуаций, когда стандартная практика кодирования С# порож­дает множество очень маленьких функций - например, для доступа к полю через свойство вместо непосредственного обращения к нему. Не повлияет ли это на производительность из-за накладных расходов, связанных с дополнительными вызовами функций? Ответ со­стоит в том, что не нужно беспокоиться о потере производительности по причине такой методологии программирования на С#. Вспомните, что код С# транслируется в IL, а затем JIT компилирует его во время выполнения в родной исполняемый код. JIT-компилятор спроектирован так, что генерирует высоко оптимизированный код и без колебаний ис­пользует встроенный (inline) код там, где это необходимо (т.е. заменяет вызовы функций встроенным кодом). Метод или свойство, чья реализация просто вызывает другой метод или возвращает поле, почти наверняка будет преобразован во встроенный код. Однако следует отметить, что решение относительно встраивания принимает исключительно CLR. У вас нет никакой возможности управлять тем, какие методы будут встроенными - например, с помощью какого-то ключевого слова вроде применяемого в С++ inline.

Конструкторы

Синтаксис объявления базовых конструкторов выглядит как объявление метода, кото­рый имеет то же имя, что и включающий его класс, и не имеет никакого типа возврата:

public class MyClass i

{

public MyClass ()

{

}

// остаток определения класса

Предусматривать конструктор в классе не обязательно. И до сих пор это не делалось ни в одном из приведенных примеров. В общем случае, если никакого конструктора не оп­ределяется, то компилятор просто создаст конструктор по умолчанию "за кулисами". Это будет конструктор очень обобщенного вида, который просто инициализирует все поля-ч­лены, обнуляя их (null-ссылки для ссылочных типов, 0 для числовых типов и false - для булевских). Часто это вполне адекватно; если же нет, то придется написать свой собствен­ный конструктор.

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

public MyClass () // конструктор без параметров

{

//код конструктора

}

public MyClass(int number) // другая перегрузка

{

//код конструктора

}

Однако обратите внимание, что если вы применяете любой конструктор с парамет­рами, то в этом случае компилятор не генерирует никакого конструктора по умолчанию. Такой конструктор генерируется только тогда, когда ни одного конструктора явно не оп­ределено. В следующем примере, поскольку определен конструктор с одним параметром, компилятор предполагает, что это - единственный конструктор, который вы хотите сде­лать доступным, а потому не применяет никакого другого:

public class MyNumber

{

private int number;

public MyNumber(int number)

{

this.number = number;

}

}

Этот код также иллюстрирует типичное использование ключевого слова this для того, чтобы отличать поля класса от параметров с теми же именами. Теперь, если попытаться создать экземпляр класса MyNumber с помощью конструктора без параметров, возникнет ошибка компиляции:

MyNumber numb = new MyNumber (); //вызовет ошибку при компиляции

Следует упомянуть также, что конструкторы можно объявлять приватными или защи­щенными - так, что они будут невидимыми коду других классов:

public class MyNumber

{

private int number;

private MyNumber(int number) // другая перегрузка

{

this.number = number;

}

}

Этот пример на самом деле не определяет никакого общедоступного или даже защи­щенного конструктора для MyNumber. Это делает невозможным создание экземпляра дан­ного класса внешним кодом с помощью операции new (хотя можно написать общедоступ­ное статическое свойство или метод, в котором будет создаваться экземпляр класса). Это удобно в двух ситуациях:

1) если класс служит лишь контейнером для некоторых статических членов или свойств, а потому его экземпляры никогда не создаются;

2) если необходимо, чтобы экземпляры класса создавались только вызовом некоторой статической функции-члена (так называемый подход "фабрик классов" к созданию объектов).

Статические конструкторы

Одним из новых средств С# является возможность написания статических конструк­торов класса без параметров. Такой конструктор выполняется лишь однажды, в противо­положность всем описанным до сих пор конструкторам, которые были конструкторами экземпляров и выполнялись при каждом создании объектов классов.

class MyClass

{

static MyClass ()

{

// код инициализации

}

// остаток определения класса

}

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

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

Единственное, что гарантируется - это то, что статический конструктор будет запущен максимум один раз и что он будет вызван до любого первого обращения к данному классу. В С# статический конструктор обычно выполняется непосредственно перед первым вызо­вом любого члена класса.

Обратите внимание, что статический конструктор не имеет никакого модификатора дос­тупа. Он никогда не вызывается никаким другим кодом С#, а всегда только исполняющей средой.NET при загрузке класса, поэтому никакой модификатор доступа вроде public или private не имеет смысла. По той же причине статический конструктор не может прини­мать никаких параметров, и у каждого класса может быть только один статический конст­руктор. Должно быть очевидным также, что статический конструктор может иметь доступ только к статическим членам класса, но не членам экземпляра.

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

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

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

namespace Wrox.ProCSharp.StaticConstructorSample

{

public class UserPreferences

{

public static readonly Color BackColor;

static UserPreferences ()

{

DateTime now = DateTime.Now;

if (now.DayOfWeek == DayOfWeek.Saturday ||

now.DayOfWeek == DayOfWeek.Sunday)

BackColor = Color.Green;

else

BackColor = Color.Red;

}

private UserPreferences ()

{

}

}

}

Приведенный код демонстрирует, как предпочтительный цвет сохраняется в статиче­ской переменной, которая инициализируется статическим конструктором. Это поле объ­явлено как доступное только для чтения, т.е. его значение может быть установлено только в конструкторе. Далее в этой главе вы узнаете подробнее о полях, доступных только для чтения. Код использует ряд удобных структур, которые поставляются Microsoft как часть библиотеки классов.NET - System.DateTime и System.Drawing.Color.DateTime.

В Color.DateTime реализовано статическое свойство Now, возвращающее текущее вре­мя, и свойство экземпляра DayOfWeek, которое определяет, на какой день недели приходит­ся дата и время. Структура Color применяется для хранения цветов. Здесь реализованы также различные статические свойства, наподобие Red и Green, кото­рые, как показано в этом примере, возвращают часто используемые цвета. Чтобы исполь­зовать Color, во время компиляции необходимо сослаться на сборку System.Drawing.dll и добавить оператор using для пространства имен System.Drawing:

using System;

using System.Drawing;

Статический конструктор можно протестировать с помощью следующего кода:

class MainEntryPoint

{

static void Main(string[] args)

{

Console.WriteLine("Предпочтение пользователя: BackColor равно: "

+ UserPreferences.BackColor.ToString());

}

}

Компиляция и запуск этого кода даст следующий вывод:

Предпочтение пользователя: BackColor равно: Color [Red]

Конечно, если код выполняется в конце недели, предпочитаемым цветом может быть Green.

Поля readonly

Концепция константы как переменной, содержащей значение, которое не может быть изменено - это нечто, что С# разделяет с большинством других языков программирова­ния. Однако константы не обязательно отвечают всем требованиям. Иногда может пона­добиться переменная, которая не должна быть изменена, но чье значение не известно до момента запуска программы. Для таких случаев в С# предусмотрен другой тип перемен­ных: поля readonly.

Ключевое слово readonly обеспечивает несколько большую гибкость, чем const, пре­доставляя возможность иметь константное поле, для нахождения начального значения которого требуются некоторые вычисления. Правило использования таких полей гласит, что вы можете присваивать им значения только в конструкторе и нигде более. Также поле readonly может принадлежать экземпляру, а не быть статическим, и потому иметь разные значения в разных экземплярах класса. Это значит, что в отличие от полей const, если вы хотите сделать поле readonly статическим, то должны явно объявить его таковым.

Предположим, что имеется MDI-программа (Multiple Document Interface - многодо­кументный интерфейс), которая редактирует документы, но по причинам лицензионной политики вы хотите ограничить количество документов, открытых одновременно. Теперь предположим, что вы продаете различные версии программы и хотите дать возможность пользователю обновлять свои лицензии с тем, чтобы открывать одновременно больше документов. Это означает, что вы можете жестко закодировать максимальное количество документов в исходном коде. Вероятно, понадобится поле, представляющее максимальное число. При каждом запуске программы это поле должно быть прочитано - возможно, из системного реестра или какого-то файлового хранилища. В результате код может выгля­деть примерно так:

public class DocumentEditor

{

public static readonly uint MaxDocuments;

static DocumentEditor ()

{

MaxDocuments = DoSomethingToFindOutMaxNumber();

}

В данном случае поле статическое, поскольку максимальное количество докумен­тов должно быть сохранено лишь однажды при каждом запуске экземпляра програм­мы. Поэтому оно инициализируется в статическом конструкторе. Если же имеется поле readonly, принадлежащее экземпляру, то оно должно инициализироваться в конструкто­ре (конструкторах) экземпляра. Например: предположительно каждый редактируемый документ имеет дагу создания, которая не должна­ изменяться пользователем (потому что это означает изменение прошлого). Кроме того, это поле является общедоступным - обычно незачем объявлять readonly-поля приватными, поскольку по определению они не могут быть модифицированы извне (то же самое касается констант).

Как уже упоминалось, даты представлены классом System.DateTime. В следующем коде используется конструктор System.DateTime, принимающий три параметра (год, месяц и день месяца; подробнее об остальных конструкторах DateTime можно узнать в документа­ции MSDN).

public class Document

{

public readonly DateTime CreationDate;

public Document()

{

// Читаем дату создания файла. Предположим, что в результате

// получаем 1 января 2010 г., но вообще даты могут быть разными

// для различных экземпляров класса.

CreationDate = new DateTime(2008, 1, 1);

}

}

В последних двух фрагментах кода CreationDate и MaxDocuments трактуются как лют бое другое поле, за исключением того, что поскольку они доступны только для чтения, им нельзя присваивать значения вне конструкторов.

void SomeMethod()

{

MaxDocuments = 10;

// Здесь - ошибка компиляции. MaxDocuments объявлен как readonly.

}

Следует отметить, что присваивать значение полю readonly в конструкторе не обя­зательно. Если этого не делать, оно останется со своим значением по умолчанию, приня­тым для конкретного типа данных, либо со значением, которым инициализируется при объявлении. Это касается как статических полей readonly, так и полей readonly уровня экземпляра.

Анонимные типы

В разделе 6.2 обсуждалось ключевое слово var, ссылающееся на неявно типизированные переменные. При использовании его вместе с ключевым словом new можно создавать анонимные типы. Анонимный тип -это просто безымянный класс, унаследованный от Object. Определение класса выводится компилятором из его инициализатора, как и в слу­чае с неявно типизированными переменными.

Если нужен объект, содержащий имя, отчество и фамилию лица, то его объявление мо­жет выглядеть так:

var captain = new {FirstName = "James", MiddleName = "T",

LastName = "Kirk"};

Это создаст объект со свойствами FirstName, MiddleName и LastName. И если вы соз­дадите другой объект, который выглядит следующим образом:

var doctor = new {FirstName = "Leonard", MiddleName = "",

LastName = "McCoy"};

то типы captain и doctor будут одинаковыми. Так, например, можно установить captain = doctor.

Если устанавливаемое в объект значение поступает от другого объекта, то инициали­затор может быть сокращен. Если уже имеется класс, содержащий свойства FirstName, MiddleName и LastName, и есть экземпляр этого класса с именем экземпляра person, то объект captain может быть инициализирован, как показано ниже:

var captain = new (person.FirstName, person.MidleName,

person.LastName};

Имена свойств из объекта person будут спроецированы на новый объект по имени captain. Таким образом, объект по имени captain будет иметь свойства FirstName, MiddleName и LastName.

Действительное имя типа этих объектов неизвестно. Компилятор создает имя этого типа, и только компилятор может его использовать. Поэтому вы не можете и не должны пытаться использовать какую-либо рефлексию типа нового объекта, потому что не получи­те согласованного результата.

Структуры

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

class Dimensions

{

public double Length;

public double Width;

}

Этот код определяет класс Dimensions, который просто сохраняет длину и ширину элемента. Возможно, вы пишете программу для расстановки мебели, которая позволит лю­дям экспериментировать на компьютере с разными вариантами расположения мебели, и требуется хранить размеры каждой единицы мебели. На первый взгляд, кажется, что здесь нарушены правила хорошего программного дизайна - тем, что поля общедоступны, но дело в том, что в данном случае вовсе не нужны все возможности, предлагаемые/классами. Все, что необходимо - два числа, которые удобнее хранить вместе, чем по отдельности. Нет необходимости во множестве методов, не требуется наследование от этого класса, и не нужны никакие возможные проблемы для.NET, связанные с обслуживанием кучи — с соответствующим влиянием на производительность - для того, чтобы просто сохранить два числа.

Как упоминалось ранее в этой главе, единственное, что следует изменить в коде, чтобы сделать этот тип структурой вместо класса - просто заменить ключевое слово class на struct:

struct Dimensions

{

public double Length;

public double Width;

}

Определение функций для структур выглядит точно так же, как определение функций для классов. В следующем коде показана структура с конструктором и свойством:

struct Dimensions

{

public double Length;

public double Width;

Dimensions(double length, double width)

{

Length=length;

Width=width;

}

public int Diagonal

{

get

{

return Math.Sqrt(Length*Length + Width*Width);

}

}

}

Структуры - это типы значений, а не ссылочные типы. Это значит, что они либо со­храняются в стеке, либо являются встроенными (последнее - если они являются частью другого объекта, хранимого в куче), и имеют те же ограничения времени жизни, что и простые типы данны<



Поделиться:


Последнее изменение этой страницы: 2016-12-27; просмотров: 846; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.128.200.165 (0.016 с.)