Статические классы и члены статических классов (Руководство по программированию в C#)
Статический класс в основном такой же, как и нестатический класс, но имеется одно отличие: нельзя создавать экземпляры статического класса. Другими словами, нельзя использовать оператор new для создания переменной типа класса. Поскольку нет переменной экземпляра, доступ к членам статического класса осуществляется с использованием самого имени класса. Например, если есть статический класс, называемый UtilityClass , имеющий открытый статический метод с именем MethodA , вызов метода выполняется, как показано в следующем примере:
UtilityClass.MethodA();
Статический класс может использоваться как обычный контейнер для наборов методов, работающих на входных параметрах, и не должен возвращать или устанавливать каких-либо внутренних полей экземпляра. Например, в библиотеке классов .NET статический класс System.Math содержит методы, выполняющие математические операции, без требования сохранять или извлекать данные, уникальные для конкретного экземпляра класса Math. Это значит, что члены класса применяются путем задания имени класса и имени метода, как показано в следующем примере.
double dub = -3.14; Console.WriteLine(Math.Abs(dub)); Console.WriteLine(Math.Floor(dub)); Console.WriteLine(Math.Round(Math.Abs(dub))); // Output: // 3.14 // -4 // 3
Как и в случае с типами всех классов, сведения о типе для статического класса загружаются средой выполнения .NET, когда загружается программа, которая ссылается на класс. Программа не может точно указать, когда загружается класс. Однако гарантируется загрузка этого класса, инициализация его полей и вызов статического конструктора перед первым обращением к классу в программе. Статический конструктор вызывается только один раз, и статический класс остается в памяти на время существования домена приложения, в котором находится программа.
Создание нестатического класса, который допускает создание только одного экземпляра самого себя, см. в документе Реализация Singleton в C#.
Ниже приведены основные возможности статического класса.
- Содержит только статические члены.
- Создавать его экземпляры нельзя.
- Является запечатанным.
- Не может содержать конструкторы экземпляров.
По сути, создание статического класса аналогично созданию класса, содержащего только статические члены и закрытый конструктор. Закрытый конструктор не допускает создания экземпляров класса. Преимущество применения статических классов заключается в том, что компилятор может проверить отсутствие случайно добавленных членов экземпляров. Таким образом, компилятор гарантирует невозможность создания экземпляров таких классов.
Статические классы запечатаны, поэтому их нельзя наследовать. Они не могут наследовать от любого класса или интерфейса, кроме Object. Статические классы не могут содержать конструктор экземпляров. Однако они могут содержать статический конструктор. Нестатические классы также должен определять статический конструктор, если класс содержит статические члены, для которых нужна нетривиальная инициализация. Дополнительные сведения см. в разделе Статические конструкторы.
Пример
Ниже приведен пример статического класса, содержащего два метода, преобразующих температуру по Цельсию в температуру по Фаренгейту и наоборот.
public static class TemperatureConverter < public static double CelsiusToFahrenheit(string temperatureCelsius) < // Convert argument to double for calculations. double celsius = Double.Parse(temperatureCelsius); // Convert Celsius to Fahrenheit. double fahrenheit = (celsius * 9 / 5) + 32; return fahrenheit; >public static double FahrenheitToCelsius(string temperatureFahrenheit) < // Convert argument to double for calculations. double fahrenheit = Double.Parse(temperatureFahrenheit); // Convert Fahrenheit to Celsius. double celsius = (fahrenheit - 32) * 5 / 9; return celsius; >> class TestTemperatureConverter < static void Main() < Console.WriteLine("Please select the convertor direction"); Console.WriteLine("1. From Celsius to Fahrenheit."); Console.WriteLine("2. From Fahrenheit to Celsius."); Console.Write(":"); string? selection = Console.ReadLine(); double F, C = 0; switch (selection) < case "1": Console.Write("Please enter the Celsius temperature: "); F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine() ?? "0"); Console.WriteLine("Temperature in Fahrenheit: ", F); break; case "2": Console.Write("Please enter the Fahrenheit temperature: "); C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine() ?? "0"); Console.WriteLine("Temperature in Celsius: ", C); break; default: Console.WriteLine("Please select a convertor."); break; > // Keep the console window open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); > > /* Example Output: Please select the convertor direction 1. From Celsius to Fahrenheit. 2. From Fahrenheit to Celsius. :2 Please enter the Fahrenheit temperature: 20 Temperature in Celsius: -6.67 Press any key to exit. */
Статический члены
Нестатический класс может содержать статические методы, поля, свойства или события. Статический член вызывается для класса даже в том случае, если не создан экземпляр класса. Доступ к статическому члены всегда выполняется по имени класса, а не экземпляра. Существует только одна копия статического члена, независимо от того, сколько создано экземпляров класса. Статические методы и свойства не могут обращаться к нестатическим полям и событиям в их содержащем типе, и они не могут обращаться к переменной экземпляра объекта, если он не передается явно в параметре метода.
Более привычно объявление нестатического класса с несколькими статическими членами, чем объявление всего класса как статического. Статические поля обычно используются для следующих двух целей: хранение счетчика числа созданных объектов или хранение значения, которое должно совместно использоваться всеми экземплярами.
Статические методы могут быть перегружены, но не переопределены, поскольку они относятся к классу, а не к экземпляру класса.
Несмотря на то, что поле не может быть объявлено как static const , поле const по своему поведению является статическим. Он относится к типу, а не к экземплярам типа. Поэтому к полям const можно обращаться с использованием той же нотации ClassName.MemberName , что и для статических полей. Экземпляр объекта не требуется.
C# не поддерживает статические локальные переменные (то есть переменные, объявленные в области действия метода).
Для объявления статических методов класса используется ключевое слово static перед возвращаемым типом члена, как показано в следующем примере:
public class Automobile < public static int NumberOfWheels = 4; public static int SizeOfGasTank < get < return 15; >> public static void Drive() < >public static event EventType? RunOutOfGas; // Other non-static fields and properties. >
Статические члены инициализируются перед первым доступом к статическому члену и перед вызовом статического конструктора, если таковой имеется. Для доступа к члену статического класса следует использовать имя класса, а не имя переменной, указывая расположение члена, как показано в следующем примере:
Automobile.Drive(); int i = Automobile.NumberOfWheels;
Если класс содержит статические поля, должен быть указан статический конструктор, который инициализирует эти поля при загрузке класса.
Вызов статического метода генерирует инструкцию вызова в промежуточном языке Microsoft (MSIL), в то время как вызов метода экземпляра генерирует инструкцию callvirt , которая также проверяет наличие ссылок на пустые объекты. Однако в большинстве случаев разница в производительности двух видов вызовов несущественна.
Спецификация языка C#
Дополнительные сведения см. в разделе Статические классы, статические члены и члены экземпляра и статические конструкторы в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
- Руководство по программированию на C#
- static
- Классы
- class
- Статические конструкторы
- Конструкторы экземпляров
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
Статическая переменная инициализируется 2 раза
Играясь с синглтонами-статикой-константами во флексе, вот на что напоролся:
ChatModel.as
package < public class ChatModel < public var someConst : String = getTrace("someConst"); public var someVar : String = getTrace("someVar"); public static const someStaticConst : String = getTrace("someStaticConst"); public static var someStaticVar : String = getTrace("someStaticVar"); public const menu : ChatMenuStructure = new ChatMenuStructure(); private static var _instance : ChatModel; public static function get instance() : ChatModel < trace("Get instance start"); if (!_instance) < _instance = new ChatModel(new T()); >trace("Get instance end"); return _instance; > public function ChatModel(t : T) < trace("ChatModel contructor 1"); >private static var getTraceCount : int = 0; public static function getTrace(str : String) : String < getTraceCount++; trace("getTrace #" + getTraceCount, str); return str; >> > internal class T
ChatMenuStructure.as
package < public class ChatMenuStructure < public const chatMenuStructureConst : String = getTrace("chatMenuStructureConst"); public var chatMenuStructureVar : String = getTrace("chatMenuStructureVar"); public function ChatMenuStructure() < trace("ChatMenuStructure constructor"); >public static function getTrace(str : String) : String < trace("ChatMenuStructure getTrace", str); return str; >public function toString() : String < return "ChatMenuStructure instance"; >> >
testSuperCall.mxml
getTrace #1 someStaticConst
getTrace #2 someStaticVar
Get instance start
getTrace #1 someConst
getTrace #2 someVar
ChatMenuStructure getTrace chatMenuStructureConst
ChatMenuStructure getTrace chatMenuStructureVar
ChatMenuStructure constructor
ChatModel contructor 1
Get instance end
menu is «ChatMenuStructure instance»
- статические переменные класса не будут инициализироваться до первого вызова инстанса этого класса или первого обращения к статической переменной, как и в яве (КО не ошибался)
- до того, как вызовется констуктор — вызовутся все статические «инициализаторы» (если это первое обращение к классу), а за ними — все локальные «инициализаторы». КО подсказывает, что каждый следующий вызов конструктора уже не будет вызывать статические инициализаторы.
- наличие-отсутствие super() в конструкторе не меняет порядок инициализации (последовательности-результаты трейсов: коммит github.com/radistao/test-super-and-static/commit/7f930 и трейсы gist.github.com/radistao/5478435)
НО.
пока я все это делал — я в вызов статического метод getTrace() , которым инициализировал константы и переменные, решил вписать счетчик getTraceCount сколько раз он вызывался (строка 32 файла ChatModel.as). Все что я с ним делал — просто инкрементил и трейсил при каждом вызове getTrace()
А теперь обратите внимание на трейсы:
getTrace #1 someStaticConst
getTrace #2 someStaticVar
Get instance start
getTrace #1 someConst
getTrace #2 someVar
статическая переменная getTraceCount проинициализировалась 2 раза! WAT?

Оклимавшись и собрав остатки моего разорванного моска я начал дальше пилить и искать, WTF! Ну, вобщем, упустив все перебраные варианты, танцы с бубном и вызовы духов умерших флексеров, я понял в чем трабла:
private static var getTraceCount : int = 0;
находится в коде ниже первых двух вызовов getTrace(). Переместив это объявление в самый «верх» класса (кода) — все заработало аки часы ( github.com/radistao/test-super-and-static/commit/a0dcd ):
ChatMenuStructure.as
package < public class ChatModel < private static var getTraceCount : int = 0; public var someConst : String = getTrace("someConst"); public var someVar : String = getTrace("someVar"); public static const someStaticConst : String = getTrace("someStaticConst"); public static var someStaticVar : String = getTrace("someStaticVar"); public const menu : ChatMenuStructure = new ChatMenuStructure(); private static var _instance : ChatModel; public static function get instance() : ChatModel < trace("Get instance start"); if (!_instance) < _instance = new ChatModel(new T()); >trace("Get instance end"); return _instance; > public function ChatModel(t : T) < trace("ChatModel contructor before super()"); super(); trace("ChatModel contructor after super()"); >public static function getTrace(str : String) : String < getTraceCount++; trace("getTrace #" + getTraceCount, str); return str; >> > internal class T
Трейсы
getTrace #1 someStaticConst getTrace #2 someStaticVar Get instance start getTrace #3 someConst getTrace #4 someVar ChatMenuStructure getTrace chatMenuStructureConst ChatMenuStructure getTrace chatMenuStructureVar ChatMenuStructure constructor ChatModel contructor before super() ChatModel contructor after super() Get instance end menu is "ChatMenuStructure instance"
- вызывается getTrace для инициализации первого статического объекта someStaticConst . В этом getTrace() дергается getTraceCount , инициализируется и инкрементится до 1
- вызывается getTrace для инициализации второго статического объекта someStaticVar . В этом getTrace() дергается уже проинициализированный getTraceCount и инкрементится до 2
- и тут приходит очередь инициализации третьего статического объекта — объявления private static var getTraceCount: int = 0; И в этом месте getTraceCount совершенно безболезненно сетится снова в 0 и начинается все с начала!

Таким образом, нельзя полагаться, что если статическая переменна находится «внизу кода» и будет дергаться раньше, то при этом она будет правильно проинициализирована.
Вот мне теперь интересно, кто виноват (и что делать): компилятор, ява-машина или флеш-плеер?
Ради интереса по-экспериментировал еще с непримитивными типами ( github.com/radistao/test-super-and-static/commit/08894 ):
GetTracesClass.as
package < public class GetTracesClass < private static var constructorCalls : int = 1; private var instanceNumber : int; public function GetTracesClass() < trace("GetTracesClass constructor called " + String(constructorCalls) + " time(s)"); instanceNumber = constructorCalls; constructorCalls++; >public function toString() : String < return "GetTracesClass instance #" + String(instanceNumber); >> >
ChatModel.as
package < public class ChatModel < public var someConst : String = getTrace("someConst"); public var someVar : String = getTrace("someVar"); public static const someStaticConst : String = getTrace("someStaticConst"); public static var someStaticVar : String = getTrace("someStaticVar"); public const menu : ChatMenuStructure = new ChatMenuStructure(); private static var _instance : ChatModel; public static function get instance() : ChatModel < trace("Get instance start"); if (!_instance) < _instance = new ChatModel(new T()); >trace("Get instance end"); return _instance; > public function ChatModel(t : T) < trace("ChatModel contructor before super()"); super(); trace("ChatModel contructor after super()"); >private static var getTraceCount : GetTracesClass = new GetTracesClass(); public static function getTrace(str : String) : String < trace("getTrace #\"" + getTraceCount + "\"", str); return str; >> > internal class T
getTrace #«null» someStaticConst
getTrace #«null» someStaticVar
GetTracesClass constructor called 1 time(s)
Get instance start
getTrace #«GetTracesClass instance #1» someConst
getTrace #«GetTracesClass instance #1» someVar
ChatMenuStructure getTrace chatMenuStructureConst
ChatMenuStructure getTrace chatMenuStructureVar
ChatMenuStructure constructor
ChatModel contructor before super()
ChatModel contructor after super()
Get instance end
menu is «ChatMenuStructure instance»
Там уже все становится более прозрачно, хотя все равно тупо: первые 2 вызова getTrace() вообще не сетят getTraceCount , хотя и обращаются к нем, а после его инициализации все дальше идет нормально. Если инициализировать getTraceCount в начала — все идет нормально ( gist.github.com/radistao/5478482 )

(У меня уже не было информации для разрыва моска, поэтому эту картинку я вставил просто так!)
Потом еще попробовал с Number — та же картина, как и с int.
Потом еще попробовал инициализировать getTraceCount не нулем, а 1 (т.е., не дефолтным значением): тогда в трейсах выпало » 2, 3, 2, 3 » (т.е., в первый раз, все-таки, было взято не дефолтное значение для int, а именно то, которое ему сетится в инициализаторе).
На что это может повлиять? Я точно не знаю, но подозреваю, что, вероятно, может повлиять на какие-нибудь статические счетчики инстансов определенного класса и ему подобные шаблоны.
Таким образом, данная статья не объясняет, а скорее наоборот, спрашивает, где проблема: в компиляторе, в ява-машине или флеш плеере? Можно ли ее решить? Лично мне кажется, что такое поведение ненормальное для языка прогаммирования.
- Adobe Flash
- Action Script
- Apache Flex
Где можно выполнять инициализацию статистической переменной класса
Кроме обычных методов и полей класс может иметь статические поля, методы, константы и инициализаторы. Например, главный класс программы имеет метод main, который является статическим:
public static void main(String[] args)
Для объявления статических переменных, констант, методов и инициализаторов перед их объявлением указывается ключевое слово static .
Статические поля
При создании объектов класса для каждого объекта создается своя копия нестатических обычных полей. А статические поля являются общими для всего класса. Поэтому они могут использоваться без создания объектов класса.
Например, создадим статическую переменную:
public class Program < public static void main(String[] args) < Person tom = new Person(); Person bob = new Person(); tom.displayId(); // 3 // изменяем Person.counter Person.counter = 8; Person sam = new Person(); sam.displayId(); // Person< private int id; static int counter=1; Person()< id = counter++; >public void displayId() < System.out.printf("Id: %d \n", id); >>
Класс Person содержит статическую переменную counter, которая увеличивается в конструкторе и ее значение присваивается переменной id. То есть при создании каждого нового объекта Person эта переменная будет увеличиваться, поэтому у каждого нового объекта Person значение поля id будет на 1 больше чем у предыдущего.
Так как переменная counter статическая, то мы можем обратиться к ней в программе по имени класса:
System.out.println(Person.counter); // получаем значение Person.counter = 8; // изменяем значение
Консольный вывод программы:
Id = 1 Id = 2 3 Id = 8
Статические константы
Также статическими бывают константы, которые являются общими для всего класса.
public class Program < public static void main(String[] args) < double radius = 60; System.out.printf("Radisu: %f \n", radius); // 60 System.out.printf("Area: %f \n", Math.PI * radius); // 188,4 >> class Math
Стоит отметить, что на протяжении всех предыдущих тем уже активно использовались статические константы. В частности, в выражении:
System.out.println("hello");
out как раз представляет статическую константу класса System. Поэтому обращение к ней идет без создания объекта класса System.
Статические инициализаторы
Статические инициализаторы предназначены для инициализации статических переменных, либо для выполнения таких действий, которые выполняются при создании самого первого объекта. Например, определим статический инициализатор:
public class Program < public static void main(String[] args) < Person tom = new Person(); Person bob = new Person(); tom.displayId(); // Person< private int id; static int counter; static< counter = 105; System.out.println("Static initializer"); >Person() < id=counter++; System.out.println("Constructor"); >public void displayId() < System.out.printf("Id: %d \n", id); >>
Статический инициализатор определяется как обычный, только перед ним ставится ключевое слово static . В данном случае в статическом инициализаторе мы устанавливаем начальное значение статического поля counter и выводим на консоль сообщение.
В самой программе создаются два объекта класса Person. Поэтому консольный вывод будет выглядеть следующим образом:
Static initializer Constructor Constructor Id: 105 Id: 106
Стоит учитывать, что вызов статического инициализатора производится после загрузки класса и фактически до создания самого первого объекта класса.
Статические методы
Статические методы также относятся ко всему классу в целом. Например, в примере выше статическая переменная counter была доступна извне, и мы могли изменить ее значение вне класса Person. Сделаем ее недоступной для изменения извне, но доступной для чтения. Для этого используем статический метод:
public class Program < public static void main(String[] args) < Person.displayCounter(); // Counter: 1 Person tom = new Person(); Person bob = new Person(); Person.displayCounter(); // Counter: 3 >> class Person < private int id; private static int counter = 1; Person()< id = counter++; >// статический метод public static void displayCounter() < System.out.printf("Counter: %d \n", counter); >public void displayId() < System.out.printf("Id: %d \n", id); >>
Теперь статическая переменная недоступна извне, она приватная. А ее значение выводится с помощью статического метода displayCounter. Для обращения к статическому методу используется имя класса: Person.displayCounter() .
При использовании статических методов надо учитывать ограничения: в статических методах мы можем вызывать только другие статические методы и использовать только статические переменные.
Вообще методы определяются как статические, когда методы не затрагиют состояние объекта, то есть его нестатические поля и константы, и для вызова метода нет смысла создавать экземпляр класса. Например:
public class Program < public static void main(String[] args) < System.out.println(Operation.sum(45, 23)); // 68 System.out.println(Operation.subtract(45, 23)); // 22 System.out.println(Operation.multiply(4, 23)); // 92 >> class Operation < static int sum(int x, int y)< return x + y; >static int subtract(int x, int y) < return x - y; >static int multiply(int x, int y) < return x * y; >>
В данном случае для методов sum, subtract, multiply не имеет значения, какой именно экземпляр класса Operation используется. Эти методы работают только с параметрами, не затрагивая состояние класса. Поэтому их можно определить как статические.
Инициализация static в классе
Может кто-нибудь дать техническое объяснение, почему нельзя инициализировать статические переменные внутри класса, а в функциях можно? Да, я знаю про const static и инициализацию static вне класса, но меня интересует техническое объяснение этого процесса.
class A < public: static int a = 10; // нельзя >;
int main() < static int b = 10; // можно >
Отслеживать
задан 25 янв 2017 в 21:53
476 4 4 серебряных знака 11 11 бронзовых знаков
one point of definition
25 янв 2017 в 22:00
Не любите struct ?
26 янв 2017 в 19:48
3 ответа 3
Сортировка: Сброс на вариант по умолчанию
Это существенно разные вещи с точки зрения языка. В первом случае вы имеете просто объявление статического члена класса с внешним связыванием (external linkage), а во втором — определение статической переменной вообще без связывания (no linkage).
Традиционно в С++ определение сущностей c (для которых требуются определения) — это задача пользователя. И инициализатор традиционно (за редкими исключениями) указывается именно в определении.
Причина этого заключается в том, что выбор единицы трансляции, в которой будет располагаться определение c внешним связыванием в классическом С++ — это часть пользовательского замысла. Т.е. пользователь выбирает, в какой объектный файл попадет это определение.
Также точное место расположения определения статического члена класса определяет его порядок инициализации (и деструкции) в рамках одной единицы трансляции. Это, с точки зрения компилятора, тоже часть пользовательского замысла.
Поэтому компилятор ждет этого решения от вас, а не пытается принимать его сам.
В С++17 появятся inline-переменные, т.е. фактически возможность при помощи ключевого слова inline сказать компилятору о том, что вас не интересует, где именно будет определен ваш статический член класса. Вот пользуясь эти синтаксисом вы и сможете наконец обойтись без явного указания места определения статического члена и, как следствие, также сможете указывать инициализатор прямо в определении класса
class a < public: static inline int a = 10; >;
Отслеживать
ответ дан 25 янв 2017 в 22:01
AnT stands with Russia AnT stands with Russia
69.2k 3 3 золотых знака 62 62 серебряных знака 140 140 бронзовых знаков
Но статические переменные ведь создаются и инициализируются в статической памяти перед запуском функции main, мы ведь можем создать и инициализировать глобальные переменные, почему то-же самое нельзя сделать и со static?
25 янв 2017 в 22:10
@nammidd: Определения классов в С++ обычно располагаются в заголовочных файла и включаются в несколько единиц трансляции. В такой ситуации трактовка объявления статического члена класса как определения ведет к множественным определениям этого члена. А это уже проблема. Попробуйте определить обычную глобальную переменную в заголовочном файле и включить его в несколько единиц трансляции — вы сразу получите ошибку множественного определения. Вот именно в избежание этой проблемы ваше объявление трактуется как просто объявление, не как определение. Определение надо делать отдельно.
25 янв 2017 в 22:13
Внутри определения класса статические члены класса лишь объявляются, а не определяются. Поэтому внутри объявления класса статические члены класса могут иметь неполный тип. Например,
#include struct A < static int a[]; >; int A::a[10]; int main()
В этой демонстрационной программе в определении класса A объявляется статический член класса — массив a , который имеет неполный тип, то есть количество элементов массива не задано.
Имейте в виду, что объявление класса может быть включено во множество единиц трансляции, в то время как определение объекта, включая статические члены класса, должно быть только в одной единице трансляции, если только объект не имеет внутреннее связывание.
Кроме того инициализация статических объектов зависит от того, как он расположен относительно других статических объектов. Например, если инициализация некоторого статического объекта зависит от значения другого объекта, то этот статический объект должен быть расположен после того объекта, от которого его инициализация зависит.
В функциях вы имеете дело с определениями объектов, если только они не объявлены со спецификатором extern . Но в последнем случае они не могут иметь инициализаторов. А в определениях классов вы имеете дело с объявлениями статических членов, а не с их определениями как объектов. С константными статическими членами классов проблем нет, так как они инициализируются констатными значениями, которые вычисляются на этапе компиляции.