Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Передача параметров в методы↑ Стр 1 из 4Следующая ⇒ Содержание книги Похожие статьи вашей тематики
Поиск на нашем сайте
Объекты и типы • Отличия между структурами и классами • Члены классов • Передача по значению и передача по ссылке • Перегрузка методов • Конструкторы и статические конструкторы • Поля, доступные только для чтения • Частичные классы • Статические классы • Класс 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; } События - это члены класса, позволяющие объекту уведомлять вызывающий код о том, Функции-члены Функции-члены - это члены, которые обеспечивают некоторую функциональность для манипулирования данными класса. Они включают методы, свойства, конструкторы, фина-лизаторы, операции и индексаторы. · Методы (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.140.198.3 (0.019 с.) |