Корпорация Microsoft разработала новый язык программирования как клей для библиотеки класов .NET Framework. Последняя версия языка на настоящий момент называется C# 2.0. Она реализована в среде разработки Visual Studio 2005. Язык C# вобрал в себя все лучшее из существующих языков высокого уровня, особенно от C++.
Завершенный проект, обработанный компилятором и линковщиком, в .NET называется сборкой ( Assembly ). Он определяется шаблоном ( Template ), выбранным на этапе создания нового проекта командой меню File/New/Project.

Можно создать исполнимую сборку ( Execute ) оконнного или консольного приложения ( Application ), а можно создать библиотечную динамически компонуемую сборку ( DLL ). Показанное окно New Project является диалоговым окном мастера оболочки, в зависимости от настройки которого мастер создаст нужный тип приложения. Сборки Windows Application (Оконное приложение) и Console Application (Консольное приложение) являются исполнимыми в среде . NET, поэтому на этапе проектирования их можно откомпилировать, скомпоновать и исполнить. Библиотечные сборки можно только компилировать, а работать они будут совместно с исполнимыми файлами.
Любой язык легче изучать на примерах консольного приложения. Консольным называется приложение, в котором стандартным устройством последовательного вывода информации является единственное окно текстового типа. Консольными удобно делать вспомогательные программы (утилиты), не требующие активного диалога с пользователем и предназначенные для выполнения больших объемов монотонной работы. Режимы работы утилит устанавливаются с помощью
Любой исполнимый проект консольного или оконного типа должен иметь по крайней мере одну точку входа, определяемую статической функцией Main(). В языке C# нет глобальных функций, как это допустимо в языке C++.Каждая функция, в том числе и функция Main(), должна принадлежать какому-нибудь классу, т.е. быть методом. Функция Main() объявляется как статический метод, не требующий создания экземпляра ( Instance ) класса. В приложении, имеющем несколько классов, функция Main () может входить в несколько из них. В этом случае для определения единственной точки входа нужно определить через окно свойств проекта, какой именно класс будет стартовым для данного приложения.
Продемонстрируем это на примере консольного приложения.
Создайте заготовку консольного приложения, выполнив команду File/New/Proiect и настроив окно мастера примерно так
Обратите внимание, что мастер создал некоторый код, достаточный, по его мнению, для начала работы над проектом консольного приложения
![]() | ![]() |
|---|
Этот код на данном этапе даже несколько избыточен. Поэтому для тренировки его несколько подсократим.
Уберите две ненужные (пока) ссылки на сборки System.Data и System.Xml
Обратите внимание, что в панели Object Browser эти ссылки также исчезли.
В панели Solution Explore r откройте файл Program.c s и удалите подключенияusing System.Collections.Generic; using System.Text;
Скорректируйте оставшийся код файла Program.cs такusing System;
namespace EntryPoint
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Точка входа в классе Program");
// Ждем нажатия клавиши Enter
Console.ReadLine();
}
}
class Class1
{
static int Main()
{
Console.WriteLine("Точка входа в классе Class1");
// Ждем нажатия клавиши Enter
Console.ReadLine();
return 1;
}
}
class Class2
{
static void Main()
{
Console.WriteLine("Точка входа в классе Class2");
// Ждем нажатия клавиши Enter
Console.ReadLine();
}
}
}
Обратите внимание, что функция Main() допускает ряд перегрузок как по количеству и типу аргументов ( сигнатуре ), так и по типу возвращаемого значения. В качестве аргумента используется строковый массив, который содержит список
Выполните проект, щелкнув на кнопке Start Debugging панели инструментов оболочки
Мы видим, что проект содержит ошибку - несколько точек входа вместо одной, что отображается в панели Error List (Список ошибок)

Ошибку можно исправить, не убирая лишние точки входа, а только настроив компилятор на восприятие одной конкретной точки входа.
Выполните команду Project/Имя_Проекта Properties главного меню оболочки (или выделите узел EntryPoint всего проекта в панели Solution Explorer и щелкните на пиктограмме Properties панели Solution Explorer или выполните команду Properties контекстного меню для этого узла)
Выделите вкладку Application окна свойств проекта и через раскрывающийся список Startup object выберите нужный класс с точкой входа
Запустите приложение, программа заработала, т.к. неопределенность исчезла.Здесь мы надуманно ввели несколько функций Main(), чтобы показать возможности оболочки. Обычно приложению .NET нужна только одна функция Main().
Возьмем следующую программу для распознавания аргументов командной строки
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
// Настройка консоли
Console.Title = "Аргументы командной строки";
Console.ForegroundColor = ConsoleColor.White;
Console.CursorVisible = false;
Console.WriteLine("\nАргументы командной строки извлекаются в цикле for");
for (int i = 0; i < args.Length; i++)
Console.WriteLine("Аргумент: {0} ", args[i]);
Console.WriteLine("\nАргументы командной строки
извлекаются в цикле foreach");
foreach (string arg in args)
Console.WriteLine("Аргумент: {0} ", arg);
Console.WriteLine("\nАргументы командной строки
извлекаются классом Environment");
string[] theArgs = Environment.GetCommandLineArgs();
Console.WriteLine("Путь к приложению: {0}", theArgs[0]);
for (int i = 1; i < theArgs.Length; i++)
Console.WriteLine("Аргумент: {0} ", theArgs[i]);
// Ждем нажатия ввода для завершения работы
Console.ReadLine();
}
}
}
Для тренировки сделаем наш код более цивилизованным, применив к нему средства факторизации, рассмотренные выше.
Выделите последовательно четыре первых блока кода в функции Main() и выполните для каждого из них команду Refactor/Extract Method (главного или контекстного меню). При этом каждому из упаковываемых в отдельную функцию блоков кода присвойте такие имена:В результате должен получиться следующий код приложения
using System;
namespace FirstConsoleApp
{
class Program
{
static void Main(string[] args)
{
ConfigureCUI();
PrintArgsFor(args);
PrintArgsForeach(args);
PrintArgsEnvironment();
// Ждем нажатия ввода для завершения работы
Console.ReadLine();
}
private static void PrintArgsEnvironment()
{
Console.WriteLine("\nАргументы командной строки
извлекаются классом Environment");
string[] theArgs = Environment.GetCommandLineArgs();
Console.WriteLine("Путь к приложению: {0}", theArgs[0]);
for (int i = 1; i < theArgs.Length; i++)
Console.WriteLine("Аргумент: {0} ", theArgs[i]);
}
private static void PrintArgsForeach(string[] args)
{
Console.WriteLine("\nАргументы командной
строки извлекаются в цикле foreach");
foreach (string arg in args)
Console.WriteLine("Аргумент: {0} ", arg);
}
private static void PrintArgsFor(string[] args)
{
Console.WriteLine("\nАргументы командной
строки извлекаются в цикле for");
for (int i = 0; i < args.Length; i++)
Console.WriteLine("Аргумент: {0} ", args[i]);
}
private static void ConfigureCUI()
{
// Настройка консоли
Console.Title = "Аргументы командной строки";
Console.ForegroundColor = ConsoleColor.White;
Console.CursorVisible = false;
}
}
}
Структура программы стала более строгой - тематические блоки кода, которые могут быть больших размеров, мы упаковали в отдельные методы, оставив в функции Main() только их вызовы.
Еще раз обратите внимание, что функция Main() всегда объявляется статической, поскольку первой получает управление, когда еще нет возможности создать экземпляр класса, которому она принадлежит. По этой же причине мы вынуждены (за нас это сделала умная оболочка) и все другие методы того же класса, которому принадлежит функция Main(), делать статическими.
Члены-функции класса или структуры называются его методами, а переменные-члены - полями. При объявлении членов класса или структуры используются модификаторы, указывающие уровень доступности полей и методов в вызывающей стороне. Они определяются следующими ключевыми словами
| Модификатор доступности | Описание |
|---|---|
| public | Открытый, общедоступный. Помечает метод или поле как доступные из |
| private | Частный, приватный. Помечает метод или поле как доступные только внутри объявившего их класса. Любой член по умолчанию считается private |
| protected | Защищенный. Объявленный член доступен классу и всем его наследникам, но не доступен внешней вызывающей стороне |
| internal | Внутренний. Член доступен внутри сборки |
| protected internal | Внутренний защищенный. Член доступен внутри сборки или в типе, созданном на основе определяющего класса внутри сборки |
Типы (классы, структуры, интерфейсы, делегаты и перечисления) тоже могут использовать модификаторы доступности, но только public или internal. Если тип явно не объявлен как public, то по умолчанию он считается internal и его могут использовать блоки кода только внутри текущей сборки. Если создается динамическая библиотека ( DLL ) для внешнего использования, то определяющие типы, вызов которых предполагается из внешнего кода, нужно объявлять с модификатором public.
Уровни доступности (области видимости) защищают от использования извне специализированные для типа блоки кода. Оставлять доступными извне нужно только завершенные, цельные упаковки программного кода.
public class MyClass{...} |
Доступен из любой внешней сборки |
internal class MyClass{...} |
Доступен внутри сборки, в которой он объявлен |
class MyClass{...} |
По умолчанию доступен внутри сборки, в которой он объявлен |
Класс является шаблоном для программного кода. По нему создаются экземпляры, которые называются объектами и занимают реальную память в компьютере при загрузке программы. Класс может содержать переменные-члены (поля) или локальные переменные внутри функций, которые также должны быть объявлены.
Поля класса при создании объекта до присваивания им конкретных значений получают начальные значения по умолчанию, соответствующие их типу
bool устанавливается значение falsestring устанавливается значение nullchar устанавливается значение '\0'nullЛокальные переменные методов не получают значений по умолчанию автоматически и поэтому должны явно их получать либо через
class MyClass
{
static void Main()
{
int localInt;
Console.WriteLine(localInt);
Console.ReadLine();
}
}
|
Ошибка компиляции. Локальная переменная метода используется без предварительной инициализации |
class MyClass
{
static void Main()
{
int localInt = 1;
Console.WriteLine(localInt);
Console.ReadLine();
}
}
|
Все хорошо. Локальная переменная метода явно инициализирована перед использованием |
class MyClass
{
public int fieldInt;
static void Main()
{
MyClass myClass = new MyClass();
Console.WriteLine(myClass.fieldInt);
Console.ReadLine();
}
}
|
Все хорошо. Поле класса автоматически инициализируется нулем |
class MyClass
{
public int fieldInt = 1;
static void Main()
{
MyClass myClass = new MyClass();
Console.WriteLine(myClass.fieldInt);
Console.ReadLine();
}
}
|
Все хорошо. Поле класса явно инициализируется значением |
class MyClass
{
public int fieldInt = 1;
public MyClass()
{
fieldInt = 5;
}
static void Main()
{
MyClass myClass = new MyClass();
Console.WriteLine(myClass.fieldInt);
Console.ReadLine();
}
}
|
Конструктор класса перезаписывает начальное значение инициализации |
Для создания более надежного кода часть работы программист перекладывает на компилятор, заставляя последний отслеживать действия программиста. Именно для этого существуют константы как особый тип переменных, значения которых изменять не предполагается. Объявление константной переменной выполняется с помощью ключевого слова const, при этом переменная должна быть инициализирована значением сразу при ее объявлении. В дальнейшем любая попытка изменить значение константной переменной приведет к ошибке компиляции.
Константы можно объявлять на уровне полей или локальных переменных. Константы нельзя инициализировать ссылкой, значение которой вычисляется во время выполнения программы, но можно инициализировать выражением, значение которого вычисляется во время компиляции. При этом значения константных полей или переменных сразу вычисляются компилятором и подставляются вместо идентификаторов констант.
В приведенном примере тип класса содержит константные поля, которые инициализируются во время компиляции
using System;
class MyClass
{
public const string MESSAGE = "Привет!";
public const double PI = 3.14;
public const bool TRUE = true;
public const bool FALSE = !TRUE;
public MyClass()
{
Console.WriteLine("{0} {1} {2} {3}",
MESSAGE, PI, TRUE, FALSE);
}
}
class Start
{
static void Main()
{
MyClass myClass = new MyClass();
Console.ReadLine();
}
}
Для отличия констант от переменных их имена принято обозначать в верхнем регистре. Поскольку во всех экземплярах класса значение константных полей класса будет одно и то же, то эти поля-константы считаются неявно статическими и в вызывающем коде к ним нужно адресоваться по имени класса, а не по имени объекта. Если константное поле или переменная определены в области видимости блока кода, то к ним можно адресоваться напрямую.
using System;
class MyClass
{
// Объявление константных полей
public const string MESSAGE = "Привет!";
public const double PI = 3.14;
public const bool TRUE = true;
public const bool FALSE = !TRUE;
}
class Start
{
static void Main()
{
// Вызов полей из вне по имени класса
Console.WriteLine("{0} {1} {2} {3}",
MyClass.MESSAGE, MyClass.PI, MyClass.TRUE, MyClass.FALSE);
// Локальная константа вызывается напрямую
const int LOCAL_VARIABLE = 123;
Console.WriteLine(LOCAL_VARIABLE);
Console.ReadLine();
}
}
Обратите внимание, что в приведенных кодах нет объявления пространства имен, тогда по умолчанию код размещается в глобальном пространстве имен. Для простых примеров это не опасно, но в больших проектах есть угроза дублирования имен типов.
Константные поля принимают значения во время компиляции и остаются неизменными на уровне класса. Но что делать, если начальные значения неизменяемых полей станут известны только во время выполнения, но после их одноразовой инициализации из вне через конструктор класса не должны больше меняться. В таких случаях их объявляют доступными только для чтения с помощью ключевого слова readonly и они становятся неизменными на уровне экземпляра класса.
using System;
class Circle
{
// Объявление константного поля с отложенной инициализацией
public readonly int radius;
public Circle(int radius)
{
this.radius = radius;// Две разных переменных!!!
}
}
class Start
{
static void Main()
{
// Инициализация константного поля при создании экземпляров
Circle circle1 = new Circle(5);
Circle circle2 = new Circle(10);
Console.WriteLine("r1={0}; r2={1}",
circle1.radius, circle2.radius);
// Попытка переприсваивания значения приведет к ошибке компиляции
// circle1.radius = 20;
// circle2.radius = 30;
Console.ReadLine();
}
}
Если убрать модификатор readonly в объявлении переменной radius, то никакой ошибки компиляции не будет. Это значит, что хоть программист и полагает, что после создания объекта значение поля меняться не будет, но нет гарантии, что внутри класса или вне его не произойдет случайного переприсваивания - компилятор на это место уже не укажет. Ключевое слово this отличает переменную-поле класса от параметра конструктора.
Подзавернем покруче. В качестве полей класса могут выступать и ссылки на объекты того же класса, и их также можно сделать неизменяемыми после инициализации конструктором.
Пусть у нас есть два заветных круга (семейный и друзей!), радиус которых никогда не должен меняться. Упакуем их в тот же класс и определим объекты доступными на уровне класса. Для этого их нужно сделать статическими. Вот этот код
using System;
class Circle
{
// Объявление неизменяемого поля с отложенной инициализацией
public readonly int radius;
// Динамически создаем объект уровня класса
// и адресуем его неизменяемым полем-ссылкой
public static readonly Circle circle1 = new Circle(5);
public static readonly Circle circle2 = new Circle(10);
public Circle(int radius)
{
this.radius = radius;
}
}
class Start
{
static void Main()
{
// Объекты создавать не нужно, они уже существуют в классе
// Просто выводим значения готовых объектов из класса
Console.WriteLine("r1={0}; r2={1}",
Circle.circle1.radius, Circle.circle2.radius);
Console.ReadLine();
}
}
Применение ключевого слова readonly гарантирует, что поля-ссылки никогда не будут адресоваться на новые объекты, кроме заветных кругов, созданных внутри класса. Применение ключевого слова static предотвращает вхождение программы в бесконечный цикл вложенных вызовов создания объектов с последующим переполнением стека памяти.

Применение слова static к членам класса ( или структуры ) устанавливает за этим членом
Статические члены-данные в памяти компьютера занимают только одну постоянную область памяти, которая выделяется для хранения их значений сразу после загрузки программы, в которой есть классы со статическими членами. Предполагается, что их можно обрабатывать и тогда, когда еще не создано ни одного экземпляра. Поэтому и методы, которые должны обрабатывать эти статические данные, также необходимо помечать как статические. Для экземплярных данных создаются свои все новые области памяти с помощью оператора new, и обычные методы, вызываемые по имени объекта, обрабатывают именно данные конкретного экземпляра.
Таким образом, обычные данные-члены и обычные методы вызываются по имени экземпляра, а статические данные и методы вызываются по имени класса.В связи с этим, статические методы не могут использовать обычные поля и обычные методы, ибо последние начинают существовать только после создания экземпляра. Но обычные методы могут использовать статические данные и статические методы, ибо последние начинают существовать сразу после загрузки программы.
Естественно, что статические методы могут иметь внутри себя локальные нестатические переменные (не путайте с полями класса). Для них память выделяется в процессе загрузки метода в стек при его вызове, а освобождается сразу после возвращения из метода.
В одном классе может одновременно объявляться любое количество членов уровня класса и членов уровня экземпляра. Но если в статическом методе попытаться использовать нестатические члены класса (обратится к обычному полю или методу), то компилятор выдаст ошибку.
Приведем пример программы генерации бросков в кости, использующей статические члены класса
using System;
class Bones // Игра в кости
{
// Объявление статического поля
private static Random rnd = new Random();
// Объявление и определение статических методов
private static int GetRandomNumber(short maxValue)
{
return rnd.Next(maxValue);
}
public static string Throw()// Бросок
{
string[] messages = new string[6] { "Единица",
"Двойка",
"Тройка",
"Четверка",
"Пятерка",
"Шестерка" };
return messages[GetRandomNumber(6)];
}
}
class Start
{
static void Main()
{
// Выводим броски
for (int i = 0; i < 10; i++)
Console.WriteLine("{0}) {1}", i + 1, Bones.Throw());
Console.ReadLine();
}
}
Метод Throw() объявлен статическим и использует вспомогательную функцию GetRandomNumber(), которая в свою очередь использует поле rnd. Поэтому все вспомогательные члены должны быть статическими. Попробуйте убрать слово static из объявления поля rnd или метода GetRandomNumber() - компилятор сразу выдаст ошибку.
Если бы метод Throw() не был объявлен как static, то нужно было бы создать экземпляр класса Bones и вызывать метод на объектном уровне. При этом поле rnd и вспомогательный метод GetRandomNumber() можно оставить статическими
using System;
class Bones // Игра в кости
{
// Объявление статического поля
private static Random rnd = new Random();
// Объявление и определение статических методов
private static int GetRandomNumber(short maxValue)
{
return rnd.Next(maxValue);
}
public string Throw()// Бросок
{
string[] messages = new string[6] { "Единица",
"Двойка",
"Тройка",
"Четверка",
"Пятерка",
"Шестерка" };
return messages[GetRandomNumber(6)];
}
}
class Start
{
static void Main()
{
// Создаем экземпляр класса (объект)
Bones cube = new Bones();
// Выводим броски
for (int i = 0; i < 10; i++)
Console.WriteLine("{0}) {1}", i + 1, cube.Throw());
Console.ReadLine();
}
}
Обе приведенных программы генерируют одинаковый алгоритм. Только в первом случае данные размещаются в самом объекте-типе (там, где размещаются коды методов), а во втором - в объекте-экземпляре типа.
Когда мы моделируем какую-то задачу с помощью класса, то часть параметров этой модели будет общей для всех экземпляров этого класса и ее разумно поместить в отдельную область памяти, доступную всем экземплярам, а не использовать для каждого экземпляра новую память с одинаковым содержимым.
Пусть, например, мы моделируем задачу учета студентов одного факультета. Класс, содержащий данные об одном студенте, будет одновременно иметь индивидуальные для студента характеристики (ФИО, возраст, ...) и общие данные, характерные для студентов одного института и факультета (название, адрес института, учебный корпус). Общие данные удобно сделать статическими и определить их один раз при загрузке программы, после чего они будут доступны всем экземплярам класса студент.
Приведем пример
using System;
class Student // Модель одного студента
{
// Члены уровня класса
private static string institute = "КИЦМ"; // Институт
public static void ChangeInstitute(string ins)
{ institute = ins; }
private static string faculty = "Горный"; // Факультет
public static void ChangeFaculty(string fac)
{ faculty = fac; }
// Члены объектного уровня
private string surName; // Фамилия
private int age, rate; // Возраст, курс
// Конструктор
public Student(string surName, int age, int rate)
{
this.surName = surName;
this.age = age;
this.rate = rate;
}
// Метод печати всей информации о студенте
public void Show()
{
Console.WriteLine("Студент {0}: ", surName);
Console.Write("Институт " + institute
+ ", факультет " + faculty);
Console.WriteLine(", возраст " + age
+ ", курс " + rate);
Console.WriteLine(); // Создание пустой строки
}
}
class Start
{
static void Main()
{
// Настройка консоли
Console.Title = "Статические и объектные члены";
Console.ForegroundColor = ConsoleColor.White;
Console.CursorVisible = false;
// Создаем массив ссылок
Student[] students = new Student[3];
// Создаем несколько студентов
students[0] = new Student("Иванов", 18, 1);
students[1] = new Student("Петров", 19, 2);
students[2] = new Student("Сидоров", 20, 3);
// Выводим студентов до реорганизации
Console.WriteLine("Студенты до реорганизации:");
for (int i = 0; i < students.Length; i++)
students[i].Show();
// Меняем общее для всех студентов
// название института и факультета
Student.ChangeInstitute("СФУ");
Student.ChangeFaculty("ФФО");
// Выводим студентов после реорганизации
Console.WriteLine(Environment.NewLine + "Студенты
после реорганизации:");
for (int i = 0; i < students.Length; i++)
students[i].Show();
Console.ReadLine();
}
}
Мы видим, что каждый экземпляр класса использует как общие для всех студентов данные, так и индивидуальные данные для каждого студента. Общие данные хранятся в одной отдельной области памяти, а индивидуальные - в каждом экземпляре студента.
В предыдущем примере мы использовали явную инициализацию общих (статических) данных. Но что делать, как быть, если статические данные нужно загружать из базы данных или файла. Для этого уже потребуются некоторые программные действия, т.е. метод. Желательно, чтобы он вызывался автоматически при создании класса. Есть такой метод, который называется статическим конструктором. Вот каким он должен удовлетворять требованиям:
Модифицируем наш последний пример, применив для инициализации общих данных статический конструктор.
using System;
class Student // Модель одного студента
{
// Члены уровня класса
private static string institute; // Институт
public static void ChangeInstitute(string ins)
{ institute = ins; }
private static string faculty; // Факультет
public static void ChangeFaculty(string fac)
{ faculty = fac; }
// Статический конструктор для инициализации
// общих данных-членов уровня класса
static Student()
{
institute = "КИЦМ";
faculty = "Горный";
}
// Члены объектного уровня
private string surName; // Фамилия
private int age, rate; // Возраст, курс
// Конструктор
public Student(string surName, int age, int rate)
{
this.surName = surName;
this.age = age;
this.rate = rate;
}
// Метод печати всей информации о студенте
public void Show()
{
Console.WriteLine("Студент {0}: ", surName);
Console.Write("Институт " + institute
+ ", факультет " + faculty);
Console.WriteLine(", возраст " + age
+ ", курс " + rate);
Console.WriteLine(); // Создание пустой строки
}
}
class Start
{
static void Main()
{
// Создаем массив ссылок
Student[] students = new Student[3];
// Создаем несколько студентов
students[0] = new Student("Иванов", 18, 1);
students[1] = new Student("Петров", 19, 2);
students[2] = new Student("Сидоров", 20, 3);
// Выводим студентов до реорганизации
Console.WriteLine("Студенты до реорганизации:");
for (int i = 0; i < students.Length; i++)
students[i].Show();
// Меняем общее для всех студентов
// название института и факультета
Student.ChangeInstitute("СФУ");
Student.ChangeFaculty("ФФО");
// Выводим студентов после реорганизации
Console.WriteLine(Environment.NewLine + "Студенты
после реорганизации:");
for (int i = 0; i < students.Length; i++)
students[i].Show();
Console.ReadLine();
}
}
Программа выдает точно такие же результаты, как в предыдущем примере.
Может потребоваться, что нужно обрабатывать сведения глобального характера, содержащиеся только в одном экземпляре (совершенно секретно или особая папка). В этом случае программист может поручить компилятору следить за этим (за своими действиями), объявив разрабатываемый класс статическим. Когда класс объявлен статическим, компилятор не допускает создания его экземпляров (как режимный отдел) и следит за тем, чтобы все члены класса были статическими.
Приведем пример работы с едиными данными:
using System;
static class Clock // Модель часов
{
// Все члены должны быть уровня класса
public static void PrintTime()
{
Console.WriteLine(DateTime.Now.ToShortTimeString());
}
public static void PrintDate()
{
Console.WriteLine(DateTime.Today.ToShortDateString());
}
}
class Start
{
static void Main()
{
// Настройка консоли
Console.Title = "Применение статического класса";
Console.ForegroundColor = ConsoleColor.White;
Console.CursorVisible = false;
// Создавать экземпляр нельзя!!!
// Clock clock = new Clock();
Clock.PrintDate();
Clock.PrintTime();
Console.ReadLine();
}
}
Результат будет таким

Часто статические классы используют при разработке утилит, которые содержат набор методов для обработки каких-то уникальных данных с получением результата. Зачем в этом случае давать возможность плодить копии этих методов и данных.
Корпорация Microsoft разработала новый язык программирования как клей для библиотеки класов .NET Framework. Последняя версия языка на настоящий момент называется C# 2.0. Она реализована в среде разработки Visual Studio 2005. Язык C# вобрал в себя все лучшее из существующих языков высокого уровня, особенно от C++.
Завершенный проект, обработанный компилятором и линковщиком, в .NET называется сборкой ( Assembly ). Он определяется шаблоном ( Template ), выбранным на этапе создания нового проекта командой меню File/New/Project.

Можно создать исполнимую сборку ( Execute ) оконнного или консольного приложения ( Application ), а можно создать библиотечную динамически компонуемую сборку ( DLL ). Показанное окно New Project является диалоговым окном мастера оболочки, в зависимости от настройки которого мастер создаст нужный тип приложения. Сборки Windows Application (Оконное приложение) и Console Application (Консольное приложение) являются исполнимыми в среде . NET, поэтому на этапе проектирования их можно откомпилировать, скомпоновать и исполнить. Библиотечные сборки можно только компилировать, а работать они будут совместно с исполнимыми файлами.
Любой язык легче изучать на примерах консольного приложения. Консольным называется приложение, в котором стандартным устройством последовательного вывода информации является единственное окно текстового типа. Консольными удобно делать вспомогательные программы (утилиты), не требующие активного диалога с пользователем и предназначенные для выполнения больших объемов монотонной работы. Режимы работы утилит устанавливаются с помощью
Любой исполнимый проект консольного или оконного типа должен иметь по крайней мере одну точку входа, определяемую статической функцией Main(). В языке C# нет глобальных функций, как это допустимо в языке C++.Каждая функция, в том числе и функция Main(), должна принадлежать какому-нибудь классу, т.е. быть методом. Функция Main() объявляется как статический метод, не требующий создания экземпляра ( Instance ) класса. В приложении, имеющем несколько классов, функция Main () может входить в несколько из них. В этом случае для определения единственной точки входа нужно определить через окно свойств проекта, какой именно класс будет стартовым для данного приложения.
Продемонстрируем это на примере консольного приложения.
Создайте заготовку консольного приложения, выполнив команду File/New/Proiect и настроив окно мастера примерно так
Обратите внимание, что мастер создал некоторый код, достаточный, по его мнению, для начала работы над проектом консольного приложения
![]() | ![]() |
|---|
Этот код на данном этапе даже несколько избыточен. Поэтому для тренировки его несколько подсократим.
Уберите две ненужные (пока) ссылки на сборки System.Data и System.Xml
Обратите внимание, что в панели Object Browser эти ссылки также исчезли.
В панели Solution Explore r откройте файл Program.c s и удалите подключенияusing System.Collections.Generic; using System.Text;
Скорректируйте оставшийся код файла Program.cs такusing System;
namespace EntryPoint
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Точка входа в классе Program");
// Ждем нажатия клавиши Enter
Console.ReadLine();
}
}
class Class1
{
static int Main()
{
Console.WriteLine("Точка входа в классе Class1");
// Ждем нажатия клавиши Enter
Console.ReadLine();
return 1;
}
}
class Class2
{
static void Main()
{
Console.WriteLine("Точка входа в классе Class2");
// Ждем нажатия клавиши Enter
Console.ReadLine();
}
}
}
Обратите внимание, что функция Main() допускает ряд перегрузок как по количеству и типу аргументов ( сигнатуре ), так и по типу возвращаемого значения. В качестве аргумента используется строковый массив, который содержит список
Выполните проект, щелкнув на кнопке Start Debugging панели инструментов оболочки
Мы видим, что проект содержит ошибку - несколько точек входа вместо одной, что отображается в панели Error List (Список ошибок)

Ошибку можно исправить, не убирая лишние точки входа, а только настроив компилятор на восприятие одной конкретной точки входа.
Выполните команду Project/Имя_Проекта Properties главного меню оболочки (или выделите узел EntryPoint всего проекта в панели Solution Explorer и щелкните на пиктограмме Properties панели Solution Explorer или выполните команду Properties контекстного меню для этого узла)
Выделите вкладку Application окна свойств проекта и через раскрывающийся список Startup object выберите нужный класс с точкой входа
Запустите приложение, программа заработала, т.к. неопределенность исчезла.Здесь мы надуманно ввели несколько функций Main(), чтобы показать возможности оболочки. Обычно приложению .NET нужна только одна функция Main().
Возьмем следующую программу для распознавания аргументов командной строки
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
// Настройка консоли
Console.Title = "Аргументы командной строки";
Console.ForegroundColor = ConsoleColor.White;
Console.CursorVisible = false;
Console.WriteLine("\nАргументы командной строки извлекаются в цикле for");
for (int i = 0; i < args.Length; i++)
Console.WriteLine("Аргумент: {0} ", args[i]);
Console.WriteLine("\nАргументы командной строки
извлекаются в цикле foreach");
foreach (string arg in args)
Console.WriteLine("Аргумент: {0} ", arg);
Console.WriteLine("\nАргументы командной строки
извлекаются классом Environment");
string[] theArgs = Environment.GetCommandLineArgs();
Console.WriteLine("Путь к приложению: {0}", theArgs[0]);
for (int i = 1; i < theArgs.Length; i++)
Console.WriteLine("Аргумент: {0} ", theArgs[i]);
// Ждем нажатия ввода для завершения работы
Console.ReadLine();
}
}
}
Для тренировки сделаем наш код более цивилизованным, применив к нему средства факторизации, рассмотренные выше.
Выделите последовательно четыре первых блока кода в функции Main() и выполните для каждого из них команду Refactor/Extract Method (главного или контекстного меню). При этом каждому из упаковываемых в отдельную функцию блоков кода присвойте такие имена:В результате должен получиться следующий код приложения
using System;
namespace FirstConsoleApp
{
class Program
{
static void Main(string[] args)
{
ConfigureCUI();
PrintArgsFor(args);
PrintArgsForeach(args);
PrintArgsEnvironment();
// Ждем нажатия ввода для завершения работы
Console.ReadLine();
}
private static void PrintArgsEnvironment()
{
Console.WriteLine("\nАргументы командной строки
извлекаются классом Environment");
string[] theArgs = Environment.GetCommandLineArgs();
Console.WriteLine("Путь к приложению: {0}", theArgs[0]);
for (int i = 1; i < theArgs.Length; i++)
Console.WriteLine("Аргумент: {0} ", theArgs[i]);
}
private static void PrintArgsForeach(string[] args)
{
Console.WriteLine("\nАргументы командной
строки извлекаются в цикле foreach");
foreach (string arg in args)
Console.WriteLine("Аргумент: {0} ", arg);
}
private static void PrintArgsFor(string[] args)
{
Console.WriteLine("\nАргументы командной
строки извлекаются в цикле for");
for (int i = 0; i < args.Length; i++)
Console.WriteLine("Аргумент: {0} ", args[i]);
}
private static void ConfigureCUI()
{
// Настройка консоли
Console.Title = "Аргументы командной строки";
Console.ForegroundColor = ConsoleColor.White;
Console.CursorVisible = false;
}
}
}
Структура программы стала более строгой - тематические блоки кода, которые могут быть больших размеров, мы упаковали в отдельные методы, оставив в функции Main() только их вызовы.
Еще раз обратите внимание, что функция Main() всегда объявляется статической, поскольку первой получает управление, когда еще нет возможности создать экземпляр класса, которому она принадлежит. По этой же причине мы вынуждены (за нас это сделала умная оболочка) и все другие методы того же класса, которому принадлежит функция Main(), делать статическими.
Члены-функции класса или структуры называются его методами, а переменные-члены - полями. При объявлении членов класса или структуры используются модификаторы, указывающие уровень доступности полей и методов в вызывающей стороне. Они определяются следующими ключевыми словами
| Модификатор доступности | Описание |
|---|---|
| public | Открытый, общедоступный. Помечает метод или поле как доступные из |
| private | Частный, приватный. Помечает метод или поле как доступные только внутри объявившего их класса. Любой член по умолчанию считается private |
| protected | Защищенный. Объявленный член доступен классу и всем его наследникам, но не доступен внешней вызывающей стороне |
| internal | Внутренний. Член доступен внутри сборки |
| protected internal | Внутренний защищенный. Член доступен внутри сборки или в типе, созданном на основе определяющего класса внутри сборки |
Типы (классы, структуры, интерфейсы, делегаты и перечисления) тоже могут использовать модификаторы доступности, но только public или internal. Если тип явно не объявлен как public, то по умолчанию он считается internal и его могут использовать блоки кода только внутри текущей сборки. Если создается динамическая библиотека ( DLL ) для внешнего использования, то определяющие типы, вызов которых предполагается из внешнего кода, нужно объявлять с модификатором public.
Уровни доступности (области видимости) защищают от использования извне специализированные для типа блоки кода. Оставлять доступными извне нужно только завершенные, цельные упаковки программного кода.
public class MyClass{...} |
Доступен из любой внешней сборки |
internal class MyClass{...} |
Доступен внутри сборки, в которой он объявлен |
class MyClass{...} |
По умолчанию доступен внутри сборки, в которой он объявлен |
Класс является шаблоном для программного кода. По нему создаются экземпляры, которые называются объектами и занимают реальную память в компьютере при загрузке программы. Класс может содержать переменные-члены (поля) или локальные переменные внутри функций, которые также должны быть объявлены.
Поля класса при создании объекта до присваивания им конкретных значений получают начальные значения по умолчанию, соответствующие их типу
bool устанавливается значение falsestring устанавливается значение nullchar устанавливается значение '\0'nullЛокальные переменные методов не получают значений по умолчанию автоматически и поэтому должны явно их получать либо через
class MyClass
{
static void Main()
{
int localInt;
Console.WriteLine(localInt);
Console.ReadLine();
}
}
|
Ошибка компиляции. Локальная переменная метода используется без предварительной инициализации |
class MyClass
{
static void Main()
{
int localInt = 1;
Console.WriteLine(localInt);
Console.ReadLine();
}
}
|
Все хорошо. Локальная переменная метода явно инициализирована перед использованием |
class MyClass
{
public int fieldInt;
static void Main()
{
MyClass myClass = new MyClass();
Console.WriteLine(myClass.fieldInt);
Console.ReadLine();
}
}
|
Все хорошо. Поле класса автоматически инициализируется нулем |
class MyClass
{
public int fieldInt = 1;
static void Main()
{
MyClass myClass = new MyClass();
Console.WriteLine(myClass.fieldInt);
Console.ReadLine();
}
}
|
Все хорошо. Поле класса явно инициализируется значением |
class MyClass
{
public int fieldInt = 1;
public MyClass()
{
fieldInt = 5;
}
static void Main()
{
MyClass myClass = new MyClass();
Console.WriteLine(myClass.fieldInt);
Console.ReadLine();
}
}
|
Конструктор класса перезаписывает начальное значение инициализации |
Для создания более надежного кода часть работы программист перекладывает на компилятор, заставляя последний отслеживать действия программиста. Именно для этого существуют константы как особый тип переменных, значения которых изменять не предполагается. Объявление константной переменной выполняется с помощью ключевого слова const, при этом переменная должна быть инициализирована значением сразу при ее объявлении. В дальнейшем любая попытка изменить значение константной переменной приведет к ошибке компиляции.
Константы можно объявлять на уровне полей или локальных переменных. Константы нельзя инициализировать ссылкой, значение которой вычисляется во время выполнения программы, но можно инициализировать выражением, значение которого вычисляется во время компиляции. При этом значения константных полей или переменных сразу вычисляются компилятором и подставляются вместо идентификаторов констант.
В приведенном примере тип класса содержит константные поля, которые инициализируются во время компиляции
using System;
class MyClass
{
public const string MESSAGE = "Привет!";
public const double PI = 3.14;
public const bool TRUE = true;
public const bool FALSE = !TRUE;
public MyClass()
{
Console.WriteLine("{0} {1} {2} {3}",
MESSAGE, PI, TRUE, FALSE);
}
}
class Start
{
static void Main()
{
MyClass myClass = new MyClass();
Console.ReadLine();
}
}
Для отличия констант от переменных их имена принято обозначать в верхнем регистре. Поскольку во всех экземплярах класса значение константных полей класса будет одно и то же, то эти поля-константы считаются неявно статическими и в вызывающем коде к ним нужно адресоваться по имени класса, а не по имени объекта. Если константное поле или переменная определены в области видимости блока кода, то к ним можно адресоваться напрямую.
using System;
class MyClass
{
// Объявление константных полей
public const string MESSAGE = "Привет!";
public const double PI = 3.14;
public const bool TRUE = true;
public const bool FALSE = !TRUE;
}
class Start
{
static void Main()
{
// Вызов полей из вне по имени класса
Console.WriteLine("{0} {1} {2} {3}",
MyClass.MESSAGE, MyClass.PI, MyClass.TRUE, MyClass.FALSE);
// Локальная константа вызывается напрямую
const int LOCAL_VARIABLE = 123;
Console.WriteLine(LOCAL_VARIABLE);
Console.ReadLine();
}
}
Обратите внимание, что в приведенных кодах нет объявления пространства имен, тогда по умолчанию код размещается в глобальном пространстве имен. Для простых примеров это не опасно, но в больших проектах есть угроза дублирования имен типов.
Константные поля принимают значения во время компиляции и остаются неизменными на уровне класса. Но что делать, если начальные значения неизменяемых полей станут известны только во время выполнения, но после их одноразовой инициализации из вне через конструктор класса не должны больше меняться. В таких случаях их объявляют доступными только для чтения с помощью ключевого слова readonly и они становятся неизменными на уровне экземпляра класса.
using System;
class Circle
{
// Объявление константного поля с отложенной инициализацией
public readonly int radius;
public Circle(int radius)
{
this.radius = radius;// Две разных переменных!!!
}
}
class Start
{
static void Main()
{
// Инициализация константного поля при создании экземпляров
Circle circle1 = new Circle(5);
Circle circle2 = new Circle(10);
Console.WriteLine("r1={0}; r2={1}",
circle1.radius, circle2.radius);
// Попытка переприсваивания значения приведет к ошибке компиляции
// circle1.radius = 20;
// circle2.radius = 30;
Console.ReadLine();
}
}
Если убрать модификатор readonly в объявлении переменной radius, то никакой ошибки компиляции не будет. Это значит, что хоть программист и полагает, что после создания объекта значение поля меняться не будет, но нет гарантии, что внутри класса или вне его не произойдет случайного переприсваивания - компилятор на это место уже не укажет. Ключевое слово this отличает переменную-поле класса от параметра конструктора.
Подзавернем покруче. В качестве полей класса могут выступать и ссылки на объекты того же класса, и их также можно сделать неизменяемыми после инициализации конструктором.
Пусть у нас есть два заветных круга (семейный и друзей!), радиус которых никогда не должен меняться. Упакуем их в тот же класс и определим объекты доступными на уровне класса. Для этого их нужно сделать статическими. Вот этот код
using System;
class Circle
{
// Объявление неизменяемого поля с отложенной инициализацией
public readonly int radius;
// Динамически создаем объект уровня класса
// и адресуем его неизменяемым полем-ссылкой
public static readonly Circle circle1 = new Circle(5);
public static readonly Circle circle2 = new Circle(10);
public Circle(int radius)
{
this.radius = radius;
}
}
class Start
{
static void Main()
{
// Объекты создавать не нужно, они уже существуют в классе
// Просто выводим значения готовых объектов из класса
Console.WriteLine("r1={0}; r2={1}",
Circle.circle1.radius, Circle.circle2.radius);
Console.ReadLine();
}
}
Применение ключевого слова readonly гарантирует, что поля-ссылки никогда не будут адресоваться на новые объекты, кроме заветных кругов, созданных внутри класса. Применение ключевого слова static предотвращает вхождение программы в бесконечный цикл вложенных вызовов создания объектов с последующим переполнением стека памяти.

Применение слова static к членам класса ( или структуры ) устанавливает за этим членом
Статические члены-данные в памяти компьютера занимают только одну постоянную область памяти, которая выделяется для хранения их значений сразу после загрузки программы, в которой есть классы со статическими членами. Предполагается, что их можно обрабатывать и тогда, когда еще не создано ни одного экземпляра. Поэтому и методы, которые должны обрабатывать эти статические данные, также необходимо помечать как статические. Для экземплярных данных создаются свои все новые области памяти с помощью оператора new, и обычные методы, вызываемые по имени объекта, обрабатывают именно данные конкретного экземпляра.
Таким образом, обычные данные-члены и обычные методы вызываются по имени экземпляра, а статические данные и методы вызываются по имени класса.В связи с этим, статические методы не могут использовать обычные поля и обычные методы, ибо последние начинают существовать только после создания экземпляра. Но обычные методы могут использовать статические данные и статические методы, ибо последние начинают существовать сразу после загрузки программы.
Естественно, что статические методы могут иметь внутри себя локальные нестатические переменные (не путайте с полями класса). Для них память выделяется в процессе загрузки метода в стек при его вызове, а освобождается сразу после возвращения из метода.
В одном классе может одновременно объявляться любое количество членов уровня класса и членов уровня экземпляра. Но если в статическом методе попытаться использовать нестатические члены класса (обратится к обычному полю или методу), то компилятор выдаст ошибку.
Приведем пример программы генерации бросков в кости, использующей статические члены класса
using System;
class Bones // Игра в кости
{
// Объявление статического поля
private static Random rnd = new Random();
// Объявление и определение статических методов
private static int GetRandomNumber(short maxValue)
{
return rnd.Next(maxValue);
}
public static string Throw()// Бросок
{
string[] messages = new string[6] { "Единица",
"Двойка",
"Тройка",
"Четверка",
"Пятерка",
"Шестерка" };
return messages[GetRandomNumber(6)];
}
}
class Start
{
static void Main()
{
// Выводим броски
for (int i = 0; i < 10; i++)
Console.WriteLine("{0}) {1}", i + 1, Bones.Throw());
Console.ReadLine();
}
}
Метод Throw() объявлен статическим и использует вспомогательную функцию GetRandomNumber(), которая в свою очередь использует поле rnd. Поэтому все вспомогательные члены должны быть статическими. Попробуйте убрать слово static из объявления поля rnd или метода GetRandomNumber() - компилятор сразу выдаст ошибку.
Если бы метод Throw() не был объявлен как static, то нужно было бы создать экземпляр класса Bones и вызывать метод на объектном уровне. При этом поле rnd и вспомогательный метод GetRandomNumber() можно оставить статическими
using System;
class Bones // Игра в кости
{
// Объявление статического поля
private static Random rnd = new Random();
// Объявление и определение статических методов
private static int GetRandomNumber(short maxValue)
{
return rnd.Next(maxValue);
}
public string Throw()// Бросок
{
string[] messages = new string[6] { "Единица",
"Двойка",
"Тройка",
"Четверка",
"Пятерка",
"Шестерка" };
return messages[GetRandomNumber(6)];
}
}
class Start
{
static void Main()
{
// Создаем экземпляр класса (объект)
Bones cube = new Bones();
// Выводим броски
for (int i = 0; i < 10; i++)
Console.WriteLine("{0}) {1}", i + 1, cube.Throw());
Console.ReadLine();
}
}
Обе приведенных программы генерируют одинаковый алгоритм. Только в первом случае данные размещаются в самом объекте-типе (там, где размещаются коды методов), а во втором - в объекте-экземпляре типа.
Когда мы моделируем какую-то задачу с помощью класса, то часть параметров этой модели будет общей для всех экземпляров этого класса и ее разумно поместить в отдельную область памяти, доступную всем экземплярам, а не использовать для каждого экземпляра новую память с одинаковым содержимым.
Пусть, например, мы моделируем задачу учета студентов одного факультета. Класс, содержащий данные об одном студенте, будет одновременно иметь индивидуальные для студента характеристики (ФИО, возраст, ...) и общие данные, характерные для студентов одного института и факультета (название, адрес института, учебный корпус). Общие данные удобно сделать статическими и определить их один раз при загрузке программы, после чего они будут доступны всем экземплярам класса студент.
Приведем пример
using System;
class Student // Модель одного студента
{
// Члены уровня класса
private static string institute = "КИЦМ"; // Институт
public static void ChangeInstitute(string ins)
{ institute = ins; }
private static string faculty = "Горный"; // Факультет
public static void ChangeFaculty(string fac)
{ faculty = fac; }
// Члены объектного уровня
private string surName; // Фамилия
private int age, rate; // Возраст, курс
// Конструктор
public Student(string surName, int age, int rate)
{
this.surName = surName;
this.age = age;
this.rate = rate;
}
// Метод печати всей информации о студенте
public void Show()
{
Console.WriteLine("Студент {0}: ", surName);
Console.Write("Институт " + institute
+ ", факультет " + faculty);
Console.WriteLine(", возраст " + age
+ ", курс " + rate);
Console.WriteLine(); // Создание пустой строки
}
}
class Start
{
static void Main()
{
// Настройка консоли
Console.Title = "Статические и объектные члены";
Console.ForegroundColor = ConsoleColor.White;
Console.CursorVisible = false;
// Создаем массив ссылок
Student[] students = new Student[3];
// Создаем несколько студентов
students[0] = new Student("Иванов", 18, 1);
students[1] = new Student("Петров", 19, 2);
students[2] = new Student("Сидоров", 20, 3);
// Выводим студентов до реорганизации
Console.WriteLine("Студенты до реорганизации:");
for (int i = 0; i < students.Length; i++)
students[i].Show();
// Меняем общее для всех студентов
// название института и факультета
Student.ChangeInstitute("СФУ");
Student.ChangeFaculty("ФФО");
// Выводим студентов после реорганизации
Console.WriteLine(Environment.NewLine + "Студенты
после реорганизации:");
for (int i = 0; i < students.Length; i++)
students[i].Show();
Console.ReadLine();
}
}
Мы видим, что каждый экземпляр класса использует как общие для всех студентов данные, так и индивидуальные данные для каждого студента. Общие данные хранятся в одной отдельной области памяти, а индивидуальные - в каждом экземпляре студента.
В предыдущем примере мы использовали явную инициализацию общих (статических) данных. Но что делать, как быть, если статические данные нужно загружать из базы данных или файла. Для этого уже потребуются некоторые программные действия, т.е. метод. Желательно, чтобы он вызывался автоматически при создании класса. Есть такой метод, который называется статическим конструктором. Вот каким он должен удовлетворять требованиям:
Модифицируем наш последний пример, применив для инициализации общих данных статический конструктор.
using System;
class Student // Модель одного студента
{
// Члены уровня класса
private static string institute; // Институт
public static void ChangeInstitute(string ins)
{ institute = ins; }
private static string faculty; // Факультет
public static void ChangeFaculty(string fac)
{ faculty = fac; }
// Статический конструктор для инициализации
// общих данных-членов уровня класса
static Student()
{
institute = "КИЦМ";
faculty = "Горный";
}
// Члены объектного уровня
private string surName; // Фамилия
private int age, rate; // Возраст, курс
// Конструктор
public Student(string surName, int age, int rate)
{
this.surName = surName;
this.age = age;
this.rate = rate;
}
// Метод печати всей информации о студенте
public void Show()
{
Console.WriteLine("Студент {0}: ", surName);
Console.Write("Институт " + institute
+ ", факультет " + faculty);
Console.WriteLine(", возраст " + age
+ ", курс " + rate);
Console.WriteLine(); // Создание пустой строки
}
}
class Start
{
static void Main()
{
// Создаем массив ссылок
Student[] students = new Student[3];
// Создаем несколько студентов
students[0] = new Student("Иванов", 18, 1);
students[1] = new Student("Петров", 19, 2);
students[2] = new Student("Сидоров", 20, 3);
// Выводим студентов до реорганизации
Console.WriteLine("Студенты до реорганизации:");
for (int i = 0; i < students.Length; i++)
students[i].Show();
// Меняем общее для всех студентов
// название института и факультета
Student.ChangeInstitute("СФУ");
Student.ChangeFaculty("ФФО");
// Выводим студентов после реорганизации
Console.WriteLine(Environment.NewLine + "Студенты
после реорганизации:");
for (int i = 0; i < students.Length; i++)
students[i].Show();
Console.ReadLine();
}
}
Программа выдает точно такие же результаты, как в предыдущем примере.
Может потребоваться, что нужно обрабатывать сведения глобального характера, содержащиеся только в одном экземпляре (совершенно секретно или особая папка). В этом случае программист может поручить компилятору следить за этим (за своими действиями), объявив разрабатываемый класс статическим. Когда класс объявлен статическим, компилятор не допускает создания его экземпляров (как режимный отдел) и следит за тем, чтобы все члены класса были статическими.
Приведем пример работы с едиными данными:
using System;
static class Clock // Модель часов
{
// Все члены должны быть уровня класса
public static void PrintTime()
{
Console.WriteLine(DateTime.Now.ToShortTimeString());
}
public static void PrintDate()
{
Console.WriteLine(DateTime.Today.ToShortDateString());
}
}
class Start
{
static void Main()
{
// Настройка консоли
Console.Title = "Применение статического класса";
Console.ForegroundColor = ConsoleColor.White;
Console.CursorVisible = false;
// Создавать экземпляр нельзя!!!
// Clock clock = new Clock();
Clock.PrintDate();
Clock.PrintTime();
Console.ReadLine();
}
}
Результат будет таким

Часто статические классы используют при разработке утилит, которые содержат набор методов для обработки каких-то уникальных данных с получением результата. Зачем в этом случае давать возможность плодить копии этих методов и данных.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.