Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
На заметку! Ссылка на нестатические члены внутри реализации статического члена приводит к ошибке на этапе компиляции. В качестве связанного замечания: ошибкой также будет применение ключевого слова this к статическому члену, потому что this подразумевает объект!
Определение статических конструкторов
Типичный конструктор используется для установки значений данных уровня экземпляра во время его создания. Однако что произойдет, если вы попытаетесь присвоить значение статическому элементу данных в типичном конструкторе? Вы можете быть удивлены, обнаружив, что значение сбрасывается каждый раз, когда создается новый объект.
В целях иллюстрации модифицируйте код конструктора класса SavingsAccount, как показано ниже (также обратите внимание, что поле currInterestRate больше не устанавливается при объявлении):
class SavingsAccount
{
public double currBalance;
public static double currInterestRate;
// Обратите внимание, что наш конструктор устанавливает
// значение статического поля currInterestRate.
public SavingsAccount(double balance)
{
currInterestRate = 0.04; // Это статические данные!
currBalance = balance;
}
...
}
Теперь добавьте к операторам верхнего уровня следующий код:
// Создать объект счета.
SavingsAccount s1 = new SavingsAccount(50);
// Вывести текущую процентную ставку.
Console.WriteLine("Interest Rate is: {0}", SavingsAccount.GetInterestRate());
// Попытаться изменить процентную ставку через свойство.
SavingsAccount.SetInterestRate(0.08);
// Создать второй объект счета.
SavingsAccount s2 = new SavingsAccount(100);
// Должно быть выведено 0.08, не так ли?
Console.WriteLine("Interest Rate is: {0}", SavingsAccount.GetInterestRate());
Console.ReadLine();
При выполнении этого кода вы увидите, что переменная currInterestRate сбрасывается каждый раз, когда создается новый объект SavingsAccount, и она всегда установлена в 0.04. Очевидно, что установка значений статических данных в нормальном конструкторе уровня экземпляра сводит на нет все их предназначение. Когда бы ни создавался новый объект, данные уровня класса сбрасываются! Один из подходов к установке статического поля предполагает применение синтаксиса инициализации членов, как делалось изначально:
class SavingsAccount
{
public double currBalance;
// Статические данные.
public static double currInterestRate = 0.04;
...
}
Такой подход обеспечит установку статического поля только один раз независимо от того, сколько объектов создается. Но что, если значение статических данных необходимо получать во время выполнения? Например, в типичном банковском приложении значение переменной, представляющей процентную ставку, будет читаться из базы данных или внешнего файла. Решение задач подобного рода обычно требует области действия метода, такого как конструктор, для выполнения соответствующих операторов кода.
По этой причине язык C# позволяет определять статический конструктор, который дает возможность безопасно устанавливать значения статических данных. Взгляните на следующее изменение в коде класса:
class SavingsAccount
{
public double currBalance;
public static double currInterestRate;
public SavingsAccount(double balance)
{
currBalance = balance;
}
// Статический конструктор!
static SavingsAccount()
{
Console.WriteLine("In static ctor!");
currInterestRate = 0.04;
}
...
}
Выражаясь просто, статический конструктор представляет собой специальный конструктор, который является идеальным местом для инициализации значений статических данных, если их значения не известны на этапе компиляции (например, когда значения нужно прочитать из внешнего файла или базы данных, сгенерировать случайные числа либо получить значения еще каким-нибудь способом). Если вы снова запустите предыдущий код, то увидите ожидаемый вывод. Обратите внимание, что сообщение "In static ctor!" выводится только один раз, т.к. среда CoreCLR вызывает все статические конструкторы перед первым использованием (и никогда не вызывает их заново для данного экземпляра приложения):
***** Fun with Static Data *****
In static ctor!
Interest Rate is: 0.04
Interest Rate is: 0.08
Ниже отмечено несколько интересных моментов, касающихся статических конструкторов.
• В отдельно взятом классе может быть определен только один статический конструктор. Другими словами, перегружать статический конструктор нельзя.
• Статический конструктор не имеет модификатора доступа и не может принимать параметры.
• Статический конструктор выполняется только один раз вне зависимости от количества создаваемых объектов заданного класса.
• Исполняющая система вызывает статический конструктор, когда создает экземпляр класса или перед доступом к первому статическому члену из вызывающего кода.
• Статический конструктор выполняется перед любым конструктором уровня экземпляра.
С учетом такой модификации при создании новых объектов SavingsAccount значения статических данных предохраняются, поскольку статический член устанавливается только один раз внутри статического конструктора независимо от количества созданных объектов.
Определение статических классов
Ключевое слово static допускается также применять прямо на уровне класса. Когда класс определен как статический, его экземпляры нельзя создавать с использованием ключевого слова new, и он может содержать только члены или поля данных, помеченные ключевым словом static. В случае нарушения этого правила возникают ошибки на этапе компиляции.
На заметку! Вспомните, что класс (или структура), который открывает доступ только к статической функциональности, часто называется обслуживающим классом. При проектировании обслуживающего класса рекомендуется применять ключевое слово static к самому определению класса.
На первый взгляд такое средство может показаться довольно странным, учитывая невозможность создания экземпляров класса. Тем не менее, в первую очередь класс, который содержит только статические члены и/или константные данные, не нуждается в выделении для него памяти. В целях иллюстрации определите новый класс по имени TimeUtilClass:
using System;
namespace StaticDataAndMembers
{
// Статические классы могут содержать только статические члены!
static class TimeUtilClass
{
public static void PrintTime()
=> Console.WriteLine(DateTime.Now.ToShortTimeString());
public static void PrintDate()
=> Console.WriteLine(DateTime.Today.ToShortDateString());
}
}
Так как класс TimeUtilClass определен с ключевым словом static, создавать его экземпляры с помощью ключевого слова new нельзя. Взамен вся функциональность доступна на уровне класса. Чтобы протестировать данный класс, добавьте