Компания Microsoft специально для платформы .NET Framework разработала язык C# (Си-шарп). В отличие от языка C++, где основой программирования является упаковка кода в классы и функции, в C# введено более общее понятие - типы. Теперь типы включают в себя на равных классы, структуры, интерфейсы, перечисления и делегаты.
| abstract | as | base | bool | break | byte | case |
| catch | char | checked | class | const | continue | decimal |
| default | delegate | do | double | else | enum | event |
| explicit | extern | false | finally | fixed | float | for |
| foreach | goto | if | implicit | in | int | interface |
| internal | is | lock | long | namespace | new | null |
| object | operator | out | override | params | private | protected |
| public | readonly | ref | return | sbyte | sealed | short |
| sizeof | stackalloc | static | string | struct | switch | this |
| throw | true | try | typeof | uint | ulong | unchecked |
| unsafe | ushort | using | virtual | void | volatile | while |
Существование самостоятельных глобальных функций, как это имеет место в C++, отменено. Теперь разработка кода программы сводится к последовательному развитию типов, главным из которых по прежнему остается класс. Каждый класс рекомендуется размещать, как самостоятельный фрагмент кода программы, в отдельном файле с расширением *.cs. Класс не принято разбивать на две части: объявление (файл *.h ) и реализацию (файл *.cpp ) - все размещается в одном файле *.cs.
Внутри класса дается объявление и реализация методов, определяющих его функциональность. Каждый класс размещается внутри именованных скобок namespace Имя{...}, задающих простанство имен. Кроме того, вначале файла с пользовательским классом перечисляются пространства имен используемых библиотечных классов. Объявление класса не должно заканчиваться точкой_с_запятой, как это принято в C++.
Запустите VS.NET и создайте новый проект консольного приложения со стандартным именем ConsoleApplication1. Это имя определит имена файлов и пространства имен, которые создаст мастер приложений по умолчанию, а также имя исполнимого файла приложения. Поэтому его нужно выбирать обдуманно.
Мастер создаст заготовку простейшего класса C#, который имеет вид
using System;
namespace ConsoleApplication1
{
///
/// Summary description for Class1.
///
class Class1
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
}
}
}
Зайдите в каталог проекта и посмотрите, что мастер создал много подкаталогов различного назначения и много файлов. Это существенно отличается от конфигурации проекта, создаваемого мастером C++. Конфигурацию проекта, созданного мастером C#, можно посмотреть в панели Solution Explorer, установив режим этой панели щелчком по кнопке Show All Files 
Через панель Solution Explorer измените имя файла Class1.cs на HelloClass.cs. Вы увидете, что автоматически поменялось имя вкладки текстового редактора исходного кода оболочки, а на диске поменялось имя файла на HelloClass.cs 
В текстовом редакторе оболочки измените имя класса Class1 на HelloClass. Убедитесь, что в панели Class View имя класса также изменилось и изменилось имя в раскрывающемся списке в верхней части рабочей области оболочки
В текстовом редакторе измените имя пространства имен проекта с C onsoleApplication1 на ConsoleApp. Отметьте изменения в раскрывающемся списке в верхней части рабочей области оболочки и в панели Class View 
Давайте посмотрим на код класса, сгенерированного мастером. Там содержится единственный метод Main(). Это имя служебное и его менять нельзя!. Оно определяет точку входа в программу, с которой начнется выполнение приложения. Метод является общедоступным или закрытым (безразлично, все равно это точка входа), статическим, ничего не возвращающим. Другие же методы класса, предназначенные для вызова из клиента, обязательно должны быть общедоступными.
Любая строка определения данных или метода должна начинаться с оператора доступа. Если оператор доступа отсутствует, то соответствующая программная единица считается внутренней по умолчанию. Это же правило полностью относится и к структурам (в отличие от языка C++ ).
Аргументами метода Main() являются
static void Main(string[] args) {...} |
public static void Main(string[] args) {...} |
public static void Main() {...} |
private static void Main(string[] args) {...} |
private static void Main() {...} |
static int Main(string[] args) {... return 0; } |
public static int Main(string[] args) {... return 0; } |
public static int Main() {... return 0; } |
private static int Main(string[] args) {... return 0; } |
private static int Main() {... return 0; } |
Заполните метод Main() следующим кодомusing System;
namespace ConsoleApp
{
///
/// Summary description for Class1.
///
class HelloClass
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
Console.WriteLine("Привет студентам от Снеткова!!!");
while(true);
}
}
}
Постройте и выполните приложение. Должен получиться следующий результат
Сейчас мы запустим приложение через командную строку, чтобы дальше исследовать механизм передачи
Нажмите кнопку Пуск панели задач TaskBar Windows.
Выполните команду системного меню Выполнить...
Введите в поле окна Запуск программы команду cmd
Используя команды cd \, cd .., dir, спуститесь вниз по дереву каталогов проекта ConsoleApplication1\bin\Debug в каталог Debug, в котором находится исполнимый файл ConsoleApplication1.exe.
Выполните команду ConsoleApplication1. Должно запуститься наше приложение.
Нажмите комбинацию клавиш Ctrl-C или Ctrl-Break, чтобы прервать работу приложения, зацикленного на бесконечном выполнении предложения while(true) ;Оставьте открытым окно командной строки и приступим к модификации нашего проекта.
Вернемся к медоту Main() нашего приложения, вернее к его заголовку. Он выглядит так
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
Console.WriteLine("Привет студентам от Снеткова!!!");
while(true);
}
Здесь квадратные скобки есть ничто иное, как псевдоним объявления экземпляра класса массива System.Array. Заголовок мог бы выглядеть так
string System.Array args
Измените метод Main() так, чтобы внутри него перебирались using System;
namespace ConsoleApp
{
///
/// Summary description for Class1.
///
class HelloClass
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
// Выводим параметры на консоль
for(int i=0; i < args.Length; i++)
{
Console.WriteLine("Arg: {0}", args[i]);
}
Console.WriteLine("Привет студентам от Снеткова!!!");
while(true);
}
}
}
Постройте приложение, выполнив команду меню оболочки Project/Build Solution
Вернитесь в окно командной строки Windows, в котором текущим должен быть установлен каталог ConsoleApplication1\bin\Debug с исполнимым файлом и наберите в командной строке следующий текст>ConsoleApplication1 Раз Два Три Четыре Пять
После нажатия клавиши Enter на экране должен появиться следующий результат
Arg: Раз Arg: Два Arg: Три Arg: Четыре Arg: Пять Привет студентам от Снеткова!!!
Нажмите комбинацию клавиш Ctrl-C или Ctrl-Break, чтобы прервать работу приложения, зацикленного на бесконечном выполнении предложения while(true) ;
Замените в методе Main() цикл for на foreach, получив такой кодusing System;
namespace ConsoleApp
{
///
/// Summary description for Class1.
///
class HelloClass
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
// Выводим параметры на консоль
/*
for(int i=0; i < args.Length; i++)
{
Console.WriteLine("Arg: {0}", args[i]);
}
*/
foreach(string s in args)
{
Console.WriteLine("Arg: {0}", s);
}
Console.WriteLine("Привет студентам от Снеткова!!!");
while(true);
}
}
}
Постройте приложение, выполнив команду меню оболочки Project/Build Solution
Вернитесь в окно командной строки Windows, в котором текущим должен быть установлен каталог ConsoleApplication1\bin\Debug с исполнимым файлом и наберите (либо нажмите клавишу со стрелкой вверх) в командной строке следующий текст>ConsoleApplication1 Раз Два Три Четыре Пять
После нажатия клавиши Enter на экране должен появиться тот же самый результат
Arg: Раз Arg: Два Arg: Три Arg: Четыре Arg: Пять Привет студентам от Снеткова!!!
Нажмите комбинацию клавиш Ctrl-C или Ctrl-Break, чтобы прервать работу приложения, зацикленного на бесконечном выполнении предложения while(true);Все объекты в C# создаются на куче и с помощью оператора new. Инициализация объектов при их создании выполняется функциями-членами класса, которые называются конструкторами. Конструкторы не нужно вызывать специально. Они вызываются автоматически при создании объекта и только один раз. Повторно вызвать конструктор как метод для уже существующего объекта невозможно.
Различают конструкторы по умолчанию и общие конструкторы. Конструктор по умолчанию не имеет аргументов. Программист может его определить сам или ситема предоставит его автоматически, если программистом не был объявлен ни один конструктор. Конструктор по умолчанию может быть у класса только один. Общие конструкторы имеют аргументы. Класс может иметь несколько общих конструкторов, только они должны различаться сигнатурой (количеством, типом и порядком следования аргументов), чтобы компилятор мог однозначно определить перегружаемый конструктор по сигнатуре в фактическом вызове.
Язык C# имеет существенное отличие от C++ в отношении конструктора по умолчанию. В C++ если программист не предусмотрел свой конструктор по умолчанию, то предоставляемый системой конструктор ничего с данными-членами класса не делает и они остаются неинициализированными. В C# конструктор по умолчанию, предоставляемый системой, производит инициализацию данных, большей частью нулем. Но если программист определил хоть один конструктор с параметрами, то конструктор по умолчанию системой уже не предоставляется и нужно определить свой конструктор по умолчанию, хоть с пустым телом.
Имена конструкторов должны совпадать с именем класса. Конструкторы должны иметь public.
Для иллюстрации сказанного продолжим модификацию нашего проекта консольного приложения ConsoleApplication1 в части объявления класса HelloClass.
Модифицируйте класс следующим образомusing System;
namespace ConsoleApp
{
class HelloClass
{
// Внутренние переменные
private int x, y;
// Конструкторы
public HelloClass() // Конструктор по умолчанию
{
Console.WriteLine("Конструктор по умолчанию");
}
public HelloClass(int a, int b) // Конструктор с параметрами
{
Console.WriteLine("Конструктор с параметрами");
x = a;
y = b;
}
// Сервисы класса
public int GetX(){return x;}
public int GetY(){return y;}
// Точка входа в программу
static void Main(string[] args)
{
// Создаем два объекта-экземпляра класса
HelloClass ob1 = new HelloClass(); // Вызов конструктора по умолчанию
Console.WriteLine("ob1: x = {0};
y = {1} \n", ob1.GetX(), ob1.GetY());
HelloClass ob2 = new HelloClass(10, 15); // Вызов общего конструктора
Console.WriteLine("ob2: x = {0};
y = {1}", ob2.GetX(), ob2.GetY());
while(true);
}
}
}
Постройте и запустите проект через кнопку Start оболочки, получим результатРезультат применения конструкторов
Конструктор по умолчанию ob1: x = 0; y = 0 Конструктор с параметрами ob2: x = 10; y = 15
Мы видим, что переменные x и y в первом объекте не остались брошенными, как это бы было в C++, а инициализировались нулями в соответствии с их типом.
Деструкторы в C# применять не нужно, поэтому они не предусмотрены синтаксисом и в C# даже нет зарезервированного слова delete. Программист не должен заботиться об освобождении затребованной у системы памяти на куче. Об этом позаботится специальный механизм, который называется сборщиком мусора ( GC - Garbare ). Сборщик мусора платформы . NET освободит память автоматически.
В последнем примере мы внутри класса HelloClass в методе Main() создавали объекты того же самого класса. Это вносит лишнюю путаницу в изучение кода. Функция Main() никакого отношения к классу HelloClass не имеет и присутствует там только из-за того, что глобальные функции в C# недопустимы и ее нужно куда-то приткнуть. Так давайте же вынесем ее в отдельный класс, чтобы отделить "котлеты от мух".
Измените код файла HelloClass.cs так, как показано нижеusing System;
namespace ConsoleApp
{
class HelloClass
{
// Внутренние переменные
private int x, y;
// Конструкторы
public HelloClass() // Конструктор по умолчанию
{
Console.WriteLine("Конструктор по умолчанию");
}
public HelloClass(int a, int b) // Конструктор с параметрами
{
Console.WriteLine("Конструктор с параметрами");
x = a;
y = b;
}
// Сервисы класса
public int GetX(){return x;}
public int GetY(){return y;}
}
//**************************************************************
class HelloApp
{
// Точка входа в программу
static void Main(string[] args)
{
// Создаем два объекта-экземпляра класса
HelloClass ob1 = new HelloClass(); // Вызов конструктора по умолчанию
Console.WriteLine("ob1: x = {0};
y = {1} \n", ob1.GetX(), ob1.GetY());
HelloClass ob2 = new HelloClass(10, 15); // Вызов общего конструктора
Console.WriteLine("ob2: x = {0};
y = {1}", ob2.GetX(), ob2.GetY());
while(true);
}
}
}
Здесь мы ничего кардинального на сделали, просто объявили новый класс и в его тело вынесли функцию Main().
Запустите приложение и получите тот же самый результатТот же результат после вынесения точки входа в самостоятельный класс
Конструктор по умолчанию ob1: x = 0; y = 0 Конструктор с параметрами ob2: x = 10; y = 15
Поменяйте местами порядок следования классов и опять постройте приложение. Ничего не изменилось. Но в C++ пришлось бы в этом случае применять упреждающее объявление класса HelloClass перед классом HelloApp, поскольку его однопроходный компилятор встретив использование класса HelloClass еще не дошел бы до объявления самого класса HelloClass.Мы в данном примере нехорошо поступили (ну, почти нехорошо), потому что принято каждый класс размещать в отдельном файле с названием, обычно совпадающем с именем класса. Исправим это.
Выполните команду Add Class в меню Project оболочки
Заполните окно мастера так
После нажатия кнопки Open мастер создаст новый файл с именем HelloApp.cs. Перенесите класс HelloApp из файла HelloClass в новый файл, при этом не забудьте изменить пространство имен на прежнее ConsoleApp. Не забудьте удалить код перенесенного класса HelloApp в прежнем файле HelloClass.cs. Окончательно новый файл должен выглядеть такusing System;
namespace ConsoleApp
{
class HelloApp
{
// Точка входа в программу
static void Main(string[] args)
{
// Создаем два объекта-экземпляра класса
HelloClass ob1 = new HelloClass(); // Вызов конструктора по умолчанию
Console.WriteLine("ob1: x = {0};
y = {1} \n", ob1.GetX(), ob1.GetY());
HelloClass ob2 = new HelloClass(10, 15); // Вызов общего конструктора
Console.WriteLine("ob2: x = {0};
y = {1}", ob2.GetX(), ob2.GetY());
while(true);
}
}
}
Постройте приложение и убедитесь, что выходные результаты остались прежнимиТот же результат после переноса класса с функцией Main() в отдельный файл
Конструктор по умолчанию ob1: x = 0; y = 0 Конструктор с параметрами ob2: x = 10; y = 15
В C++ в классе можно простые переменные только объявлять, а инициализировать их нужно в конструкторе или устанавливать путем присваивания функциями Set() из клиента. В C# такое ограничение отсутствует и данные-члены класса можно инициализировать при их объявлении в классе. Примеры
class Text
{
private int MyInt = 90;
private string MyString = "My initial value";
private HotRod viper = new HotRod(200, "Chucky", Color.Red);
.............................................................
}
Измените код класса HelloClass нашего примера так, как показано нижеusing System;
namespace ConsoleApp
{
class HelloClass
{
// Внутренние переменные
private int x = 100, y = 150;
// Конструкторы
public HelloClass() // Конструктор по умолчанию
{
Console.WriteLine("Конструктор по умолчанию");
}
public HelloClass(int a, int b) // Конструктор с параметрами
{
Console.WriteLine("Конструктор с параметрами");
x = a;
y = b;
}
// Сервисы класса
public int GetX(){return x;}
public int GetY(){return y;}
}
}
После построения проекта получим результатРезультат после прямой инициализации данных
Конструктор по умолчанию ob1: x = 100; y = 150 Конструктор с параметрами ob2: x = 10; y = 15
Важное замечание
Отметим, что подобная прямая инициализация членов-данных в структурах недопустима. Перегрузка конструктора по умолчанию в структурах тоже недопустима.
В C# различают структурные типы ( reference-based ) и ссылочные ( value-based ). Переменная структурного типа представляет закрепленное за ней само значение оперативной памяти. Переменная ссылочного типа содержит лишь адрес области оперативной памяти с размещаемым в этой области значением. Все структурные переменные размещаются на стеке, а ссылочные - на управляемой куче ( managed ). К структурным типам относятся перечисления и структуры, а к ссылочным - классы, массивы, указатели, интерфейсы.
Когда выполняется присваивание одной структурной переменной другой, то создается побитовая копия данных, которые размещены в разных областях стековой памяти и существуют независимо друг от друга. При выполнении присваивания ссылочных переменных создается копия адреса, после чего обе переменные будут ссылаться на одну и ту же область памяти на управляемой куче. То есть создается как-бы псевдонимы одной и той же области памяти. Это означает, что изменения объекта по одной ссылочной переменной равноценно изменениям по псевдониму
Создайте консольное приложение C# или модифицируйте уже созданное. Заполните файл с точкой входа следующим кодомusing System;
namespace Test
{
struct FOO
{
public int x, y;
}
class ValRefClass
{
static void Main(string[] args)
{
// Создаем объект на стеке
// При создании структуры с конструктором по умолчанию
// ключевое слово new является необязательным,
// а можно просто FOO f1;
FOO f1 = new FOO();
f1.x = 100;
f1.y = 100;
// Создается объект на стеке без ключевого слова new
// и осуществляется побитовое копирование в другую память
FOO f2 = f1;
// Вывод до изменения
Console.WriteLine("Содержимое объектов
f1 и f2 до изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
// Внесение изменений
f1.x = 1; f1.y = 2; f2.x = 3; f2.y = 4;
// Вывод после изменения
Console.WriteLine("\nСодержимое объектов
f1 и f2 после изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
while(true);
}
}
}
Результаты экрана
Содержимое объектов f1 и f2 до изменения Объект f1: x=100; y=100 Объект f2: x=100; y=100 Содержимое объектов f1 и f2 после изменения Объект f1: x=1; y=2 Объект f2: x=3; y=4
Измените в исходном коде для типа FOO ключевое слово struct на class, тем самым сразу получим ссылочный тип. Запустите программу - получится результатusing System;
namespace Test
{
class FOO
{
public int x, y;
}
class ValRefClass
{
static void Main(string[] args)
{
// Создаем объект на куче
// Теперь слово new является обязательным, а также
// обазательны круглые скобки конструктора по умолчанию
FOO f1 = new FOO();
f1.x = 100;
f1.y = 100;
// Создается не объект, а ссылку, и после копирования
// адреса получаем псевдоним на ту же область памяти
FOO f2 = f1;
// Вывод до изменения
Console.WriteLine("Содержимое объектов f1 и
f2 до изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
// Внесение изменений
f1.x = 1; f1.y = 2; f2.x = 3; f2.y = 4;
// Вывод после изменения
Console.WriteLine("\nСодержимое объектов f1 и
f2 после изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
while(true);
}
}
}
Результаты экрана
Содержимое объектов f1 и f2 до изменения Объект f1: x=100; y=100 Объект f2: x=100; y=100 Содержимое объектов f1 и f2 после изменения Объект f1: x=3; y=4 Объект f2: x=3; y=4
Здесь мы видим, что в обоих случаях мы изменяли один и тот же объект, который и сохранил последние значения. Теперь f1 и f2 - не более чем ссылки на один и тот же диапазон памяти в области управляемой кучи.
Для того, чтобы объекты, адресуемые переменными f1 и f2, занимали разные области памяти, нужно запросить память у операционной системы для каждого из них и сохранить возвращенные адреса в этих переменных.
Следующий код и полученный результат иллюстрируют сказанное
using System;
namespace Test
{
class FOO
{
public int x, y;
}
class ValRefClass
{
static void Main(string[] args)
{
// Создаем первый объект на куче
FOO f1 = new FOO();
f1.x = 100;
f1.y = 100;
// Создается второй объект на куче
FOO f2 = new FOO();
f2.x = 100;
f2.y = 100;
// Вывод до изменения
Console.WriteLine("Содержимое объектов f1 и
f2 до изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
// Внесение изменений
f1.x = 1; f1.y = 2; f2.x = 3; f2.y = 4;
// Вывод после изменения
Console.WriteLine("\nСодержимое объектов
f1 и f2 после изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
while(true);
}
}
}
Результаты экрана
Содержимое объектов f1 и f2 до изменения Объект f1: x=100; y=100 Объект f2: x=100; y=100 Содержимое объектов f1 и f2 после изменения Объект f1: x=1; y=2 Объект f2: x=3; y=4
Поставим некоторые предварительные вопросы и ответы на них в таблице
| Вопрос | Структурные типы | Ссылочные типы |
|---|---|---|
| Место размещения | Стек | Управляемая куча |
| Как будет представлена переменная, которой в качестве значения присвоен этот тип | В виде локальной побитовой копии типа | В виде указателя на область оперативной памяти, относящейся к объекту этого типа |
| Что может являться для них базовым типом | Эти типы могут производиться только напрямую от типа System.ValueType |
Могут производиться от любого другого типа, кроме System.ValueType, если этот тип не является закрытым |
| Может ли этот тип выступать производителем другого типа | Нет. Структурные типы всегда закрыты и дополнение их другими свойствами не предусмотрено. Поэтому они не могут выступать при наследовании в качестве базового для других типов | Да, если этот ссылочный тип не определен внутренне как закрытый |
| Как производится передача параметров этого типа в функцию | Только по значению, т.е. вызывающей функции передаются локальные копии значений переменных | Передаются как ссылки, т.е. вызывающей функции передаются адреса размещения соответствующих объектов в оперативной памяти |
Существует ли возможность переопределить встроенный метод Object.Finalize() |
Нет. Структурные типы никогда не размещаются в куче, поэтому к ним нельза применить функцию освобождения памяти | Да, но не напрямую (подробнее дальше...) |
| Можно ли определить конструкторы для этого типа | Да, но конструктор по умолчанию зарезервирован, поэтому все определяемые конструкторы должны иметь параметры | Да, сколько угодно, лишь бы они различались сигнатурой |
| Когда происходит разрушение переменной данного типа | Когда происходит выход из области видимости | Во время процесса сборки мусора в управляемой куче с помощью механизма Garbare (сборщик мусора) |
| Можно ли напрямую инициализировать данные при объявлении типа | Нет | Да |
| Можно ли перегружать конструктор по умолчанию | Нет, он зарезервирован за системой | Да, и обязательно, если определен хоть один общий конструктор |
Несмотря на все различия, структурные и ссылочные типы могут иметь и поддерживать как встроенные (стандартные), так и определенные пользователем (программистом) методы, свойства, события в необходимом количестве. Но удобно за структурными типами закреплять только поддержку сложных данных.
Продемонстрируем сказанное простым примером.
Внесите следующие изменения в файл Test.cs консольного приложения C# с именем Testusing System;
namespace Test
{
//**********************************************************
struct PERSON // Структурный тип
{
private string Name;// Прямая инициализация не допускается
private int Age;// Прямая инициализация не допускается
//****************************************
// Объявления перегружаемых конструкторов
// Конструктор по умолчанию перегружать нельзя!!!
//****************************************
public PERSON(string name, int age)
{
Name = name; Age = age;
}
//****************************************
public PERSON(int age, string name)
{
Age = age; Name = name;
}
//****************************************
// Сервисы
//****************************************
public string GetName()
{
return Name;
}
//****************************************
public void SetName(string name)
{
Name = name;
}
//****************************************
public int GetAge()
{
return Age;
}
//****************************************
public void SetAge(int age)
{
Age = age;
}
}
//**********************************************************
class Person // Ссылочный тип
{
private string Name = "Иванов";// Прямая инициализация допустима
private int Age = 20;// Прямая инициализация допустима
//****************************************
// Перегружаемые конструкторы
// Конструктор по умолчанию перегружать можно
//****************************************
public Person(){}
//****************************************
public Person(string name, int age)
{
Name = name; Age = age;
}
//****************************************
public Person(int age, string name)
{
Age = age; Name = name;
}
//****************************************
public Person(string name)
{
Name = name;
}
//****************************************
public Person(int age)
{
Age = age;
}
//****************************************
// Сервисы
//****************************************
public string GetName()
{
return Name;
}
//****************************************
public void SetName(string name)
{
Name = name;
}
//****************************************
public int GetAge()
{
return Age;
}
//****************************************
public void SetAge(int age)
{
Age = age;
}
}
class ValRefClass
{
static void Main()
{
// Создаем объект на стеке
PERSON petrov = new PERSON("Денис
Петров", 23);// Общий конструктор
PERSON empty = new PERSON();// Другой объект с конструктором по умолчанию
// Круглые скобки обязательны
// Создаем объект на куче
Person sergey = new Person(21, "
Сережа Жук!");
Person ivanov = new Person(); // Создание с конструктором по умолчанию
// Вывод из стека
Console.WriteLine("Содержимое объектов на стеке");
Console.WriteLine("Объект petrov: Имя={0};
возраст={1}",petrov.GetName(), petrov.GetAge());
Console.WriteLine("Объект empty: Имя={0};
возраст={1}",empty.GetName(), empty.GetAge());
Console.WriteLine("\n");
// Вывод из кучи
Console.WriteLine("Содержимое объектов на куче");
Console.WriteLine("Объект sergey: Имя={0};
возраст={1}",sergey.GetName(), sergey.GetAge());
Console.WriteLine("Объект ivanov: Имя={0};
возраст={1}",ivanov.GetName(), ivanov.GetAge());
while(true);
}
}
}
Результаты экрана
Содержимое объектов на стеке Объект petrov: Имя=Денис Петров; возраст=23 Объект empty: Имя=; возраст=0 Содержимое объектов на куче Объект sergey: Имя=Сережа Жук!; возраст=21 Объект ivanov: Имя=Иванов; возраст=20
Все типы в C#, как структурные, так и ссылочные, являются производными от одного общего предка - класса System.Object. Это подразумевается по умолчанию и нет необходимости явно указывать этот базовый класс при объявлении
class Hello // Предок System.Object подразумевается по умолчанию
{
....................
}
struct HELLO // Предок System.Object подразумевается по умолчанию
{
....................
}
class Hello : System.Object
{
....................
}
struct HELLO : System.Object
{
....................
}
using System;
namespace Test
{
class Hello : Object // Укороченно, т.к. пространство имен определено
{
....................
}
struct HELLO : object // С маленькой буквы, потому что предусмотрен синоним
{
....................
}
}
Ниже приведен код объявления библиотечного класса System.Object
namespace System
{
public class Object
{
public Object();
public virtual Boolean Equals(Object obj);
public virtual Int32 GetHashCode();
public Type GetType();
public virtual String ToString();
protected virtual void Finalize();
protected Object MemberwiseClone();
}
}
Предназначение каждого метода приведено в таблице
| Метод | Описание |
|---|---|
Equals() |
Возвращает true, если сравниваемые сущности занимают одну и ту же область памяти. По умолчанию настроен для сравнивания объектов ссылочных типов. Для применения к объектам структурных типов этот метод и сопутствующий ему метод GetHashCode() нужно перегрузить своими методами. |
GetHashCode() |
Возвращает целочисленное значение ID, идентифицирующее конкретный экземпляр объекта данного типа. Генерирует уникальный хэш. |
GetType() |
Метод возвращает объект Type(), полностью описывающий тот объект, из которого этот метод был вызван. Это метод идентификации времени выполнения ( Runtime Type Identification - RTTI ) , который предусмотрен во всех объектах. |
ToString() |
Возвращает fully - полностью определенное имя. Если тип определен вне какого-либо пространства имен, возвращается только имя класса без именного пути. Этот метод может быть перегружен для предоставления информации о внутреннем состоянии объекта в формате имя-значение. |
Finalize() |
Основное назначение этого метода - освободить все ресурсы, занятые объектом данного класса, перед его удалением для корректной работы сборщика мусора gc - Garbare . |
MemberwiseClone() |
Этот метод предназначен для создания еще одной ссылки на область, занимаемую объектом данного типа в оперативной памяти. Этот метод не может быть перегружен. Если требуется реализовать поддержку создания полной копии объекта в оперативной памяти, то необходимо реализовать поддержку интерфейса |
В следующем примере продемонстрированы возможности некотороых из перечисленных методов
using System;
namespace Test
{
//*******************************************************
class ObjTest
{
}
//*******************************************************
class App
{
static void Main()
{
// Создаем объект на куче
ObjTest obj1 = new ObjTest();
// Выводим информацию, возвращенную методами
// базового класса System.Object
Console.WriteLine("ToString(): {0}",
obj1.ToString());
Console.WriteLine("GetHashCode(): {0}",
obj1.GetHashCode());
Console.WriteLine("GetType(): {0}",
obj1.GetType().ToString());
// Создаем еще две ссылки на объект obj1
ObjTest obj2 = obj1;
object obj3 = obj2;
// Проверяем, действительно ли все три ссылки
// указывают на одну и ту же область оперативной памяти
if(obj1.Equals(obj2) obj2.Equals(obj3))
Console.WriteLine("Адресация памяти совпадает");
while(true);
}
}
}
Результаты экрана
ToString(): Test.ObjTest GetHashCode(): 2 GetType(): Test.ObjTest Адресация памяти совпадает
Перегружать в производном классе можно только методы, объявленные в базовом классе как виртуальные с префиксом virtual. Перегружаемый метод должен маркироваться ключевым словом override.
Методы, которые наследуются типами от базового класса System.Object, иногда могут не устраивать пользователя (программиста) при разработке своих типов. Поэтому может возникнуть необходимость перегружать некоторые из них. Покажем это на примерах.
Разработаем класс Person, в котором перегрузим метод ToString() так, чтобы он выдавал не имя класса, а внутреннее состояние объекта, из которого он вызывается.
Перегрузим, также, метод Equals() базового класса. Вспомним, что он возвращает значение true в случае, если сравниваемые ссылки указывают на один и тот же объект оперативной памяти. Пусть этот метод возвращает true, если у сравниваемых объектов одинакового типа совпадает внутреннее состояние, т.е. значения всех внутренних переменных совпадают.
Если мы перегружаем метод Equals(), то обязательно нужно перегрузить и сопутствующий ему метод GetHashCode(). Если етого не сделать, то компилятор выдаст предупреждение. Метод GetHashCode() возвражает числовое значение уникального идентификатора ID объекта в памяти. Чаще всего это значение используется в коллекциях, работающих с хэшами объектов. Нам достаточно применить эту функцию к любой переменной типа, производного от базового класса System.Object. Но любые встроенные в C# типы происходят от этого базового класса.
using System;
namespace Test
{
//**********************************************************************
// Класс, хранящий информацию о человеке
//**********************************************************************
class Person
{
//******************************************
// Закрытые (private) данные о человеке
//******************************************
string Name;// ФИО
string Inn; // Номер налогоплательщика
int Age; // Возраст
//******************************************
// Конструкторы
//******************************************
public Person(){} // Пустой конструктор по умолчанию
//******************************************
public Person(string name, string inn, int age)
{
Name = name;
Inn = inn;
Age = age;
}
//******************************************
// Сервисы
//******************************************
public void Set(string name, string inn, int age)
{
Name = name;
Inn = inn;
Age = age;
}
//******************************************
public string GetName()
{
return Name;
}
//******************************************
public string GetInn()
{
return Inn;
}
//******************************************
public int GetAge()
{
return Age;
}
//******************************************
// Перегрузка библиотечного метода ToString(),
// унаследованного от класса System.Object
//******************************************
public override string ToString()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("Ф.И.О. = " + this.Name);
sb.Append("\nИНН = " + this.Inn);
sb.Append("\nВозраст = " + this.Age);
return sb.ToString ();// Генерируется автоматически
}
//******************************************
// Перегрузка библиотечного метода Equals(),
// унаследованного от класса System.Object
//******************************************
public override bool Equals(object obj)
{
Person tmp = (Person) obj; // Создаем еще одну ссылку
// Проверяем совпадения значений переменных
// текущего объекта this и переданного объекта obj
if(tmp.Name == this.Name
tmp.Inn == this.Inn
tmp.Age == this.Age)
return true;
else
return false;
}
//******************************************
// Перегрузка библиотечного метода GetHashCode(),
// связанного с методом Equals()
//******************************************
public override int GetHashCode()
{
// Применяем к переменной Inn производного типа string
return Inn.GetHashCode();
}
}
//**********************************************************************
// Клиент Main()
// Точка входа в приложение
//**********************************************************************
class App
{
static void Main()
{
// Создаем два объекта с одинаковыми состояниями
Person person1 = new Person("Серега Жук",
"2222-33-5555", 21);
Person person2 = new Person("Серега Жук",
"2222-33-5555", 21);
Console.WriteLine("Прервая проверка для
одинаковых объектов");
// Используем замещенный для сравнения внутренного сотояния метод Equals()
if(person1.Equals(person2) person1.GetHashCode() ==
person2.GetHashCode())
Console.WriteLine("Состояние объектов person1 и
person2 совпадает");
else
Console.WriteLine("Состояние объектов person1 и
person2 несовпадает");
// Меняем состояние одного из объектов
person2.Set("Андрей Косенок",
"1111-22-4444", 22);
Console.WriteLine("\nПовторная проверка для
различающихся объектов");
// Используем замещенный для сравнения внутренного
сотояния метод Equals()
if(person1.Equals(person2) person1.GetHashCode() ==
person2.GetHashCode())
Console.WriteLine("Состояние объектов person1 и
person2 совпадает");
else
Console.WriteLine("Состояние объектов person1 и
person2 несовпадает");
// Теперь используем замещенный (перегруженный) метод ToString()
Console.WriteLine("\nСодержимое объекта person1");
Console.WriteLine(person1.ToString());
Console.WriteLine("\nСодержимое объекта person2");
// Метод WriteLine() сам автоматически вызывает метод ToString()
Console.WriteLine(person2);
while(true);
}
}
}
Результаты экрана
Прервая проверка для одинаковых объектов Состояние объектов person1 и person2 совпадает Повторная проверка для различающихся объектов Состояние объектов person1 и person2 несовпадает Содержимое объекта person1 Ф.И.О. = Серега Жук ИНН = 2222-33-5555 Возраст = 21 Содержимое объекта person2 Ф.И.О. = Андрей Косенок ИНН = 1111-22-4444 Возраст = 22
Мы испробовали работу метода Equals() в двух вариантах:
true, если ссылки имеют одинаковое значение, что означает, что обе ссылки указывают на один и тот же объектНо разработчики класса System.Object предусмотрели для обоих этих целей специално два статических метода
System.Object.Equals() - сравнивает значения составляющих-членов двух объектовSystem.Object.ReferenceEquals() - сравнивает значения ссылок на два объектаИспытаем эти методы, изменив соответствующим образом код предыдущего примера. При этом следует помнить, что эти два метода статические. Это значит, что они существуют применительно к классу, а не к конкретному объекту. И вызывать их нужно с использованием имени класса. И еще напомним, что для класса Object простанства имен System определен псевдоним object как стандартный (встроенный) тип языка C#.
И так, листинг примера...
using System;
namespace Test
{
//**********************************************************************
// Класс, хранящий информацию о человеке
//**********************************************************************
class Person
{
//******************************************
// Закрытые (private) данные о человеке
//******************************************
string Name;// ФИО
string Inn; // Номер налогоплательщика
int Age; // Возраст
//******************************************
// Конструкторы
//******************************************
public Person(){} // Пустой конструктор по умолчанию
//******************************************
public Person(string name, string inn, int age)
{
Name = name;
Inn = inn;
Age = age;
}
}
//**********************************************************************
// Клиент Main()
// Точка входа в приложение
//**********************************************************************
class App
{
static void Main()
{
// Создаем два объекта с одинаковыми состояниями
Person person1 = new Person("Sally",
"2222-33-5555", 21);
Person person2 = new Person("Sally",
"2222-33-5555", 21);
string p1 = "Привет всем!";
string p2 = "Привет всем!";
// Сравниваем внутреннее состояние двух объектов
Console.WriteLine("Содержимое двух объектов
одинаково? - {0}", object.Equals(p1, p2));
// Сравниваем значения ссылок
Console.WriteLine("Занимают они одну память? - {0}",
object.ReferenceEquals(person1, person2));
while(true);
}
}
}
Результаты экрана
Содержимое двух объектов одинаково? - True Занимают они одну память? - False
Любой встроенный в C# тип данных - это лишь псевдоним для существующего типа, определенного в пространстве имен System. В таблице приведены все системные типы данных и соответствующие им псевдонимы C#, а также информация о совместимости с общей средой выполнения CLR ( (Common Language Specification).
| Псевдоним C# | Соответствие CLS | Системный тип | Диапазон | Пояснения |
|---|---|---|---|---|
void |
+ | Void |
Пусто | Применяется для соблюдения синтаксиса |
sbyte |
- | SByte |
-128 —127 | Знаковое 8 бит |
byte |
+ | Byte |
0 —255 | Беззнаковое 8 бит |
short |
+ | Int16 |
-32'768 —32'767 | Знаковое 16 бит |
ushort |
- | UInt16 |
0 —65'535 | Беззнаковое 16 бит |
int |
+ | Int32 |
-2'147'483'648 —2'147'483'647 | Знаковое 32 бит |
uint |
- | UInt32 |
0 —4'294'967'295 | Беззнаковое 32 бит |
long |
+ | Int64 |
-9'223'372'036'854'775'808 —9'223'372'036'854'775'807 | Знаковое 64 бит |
ulong |
- | UInt64 |
0 —18'446'774'073'709'551'615 | Беззнаковое 64 бит |
char |
+ | Char |
U+0000 —U+FFFF | Для описания только одного символа Unicode 16 бит |
float |
+ | Single |
1.5x10-45 —3.4x1038 | Знаковое с плавающей точкой 32 бит |
double |
+ | Double |
5.0x10-324— 1.7x10308 | Знаковое с плавающей точкой 64 бит |
bool |
+ | Boolean |
true или false |
Логическое |
decimal |
+ | Decimal |
100 —1028 | Знаковое 96 бит |
string |
+ | String |
Ограничено только системной памятью | Для описания строки символов Unicode любой длины |
object |
+ | Object |
Практически все что угодно. Все типы происходят от System.Object, поэтому объектом является все |
Класс, базовый для всех типов в . NET |
Из приведенных в таблице типов только часть является CLR -совместимой. Если мы создаем приложение, которое должно работать в многоязыковой среде, то желательно ограничиться только -NET.
Системные типы как классы можно посмотреть в суфлере кода IntelliSense, написав в текстовом редакторе слово System и точку

То же самое можно получить, выполнив команду запуска IntelliSense как показано на картинке

То же самое можно получить, написав в текстовом редакторе кода внутри любого метода ключевое слово new

Во всех случаях при этом нужно, чтобы курсор конструктивно находился внутри какого-нибудь метода класса или структуры.
Поскольку тип int - это всего лишь более простая и удобная запись системного типа Int32, то и с числовыми константами можно обращаться как с переменными соответствующего типа. Ниже приведен пример допустимого синтаксиса
using System;
namespace Test
{
class Test
{
}
class App
{
static void Main()
{
Console.WriteLine("ToString(): {0}", 1234567.ToString());
while(true);
}
}
}
Результат выполнения примера
ToString(): 1234567
Большинство встроенных типов являются структурными типами, потому что происходят не от базового типа System.Object напрямую, а от производного от System.Object типа System.ValueType. Этот класс-надстройка нужен как раз для того, чтобы переменные системных типов были структурными и представлялись значением, а не ссылкой. Приведем пример
using System;
namespace Test
{
class App
{
static void Main()
{
System.Int32 xInt = 100;
System.Int32 yInt = 100;
System.Object xObj = 1000;
System.Object yObj = 1000;
// Сравниваются значения
if(xInt == yInt)
Console.WriteLine("Значения одинаковые");
else
Console.WriteLine("Значения различаются");
// Сравниваются ссылки
if(xObj == yObj)
Console.WriteLine("Ссылки одинаковые");
else
Console.WriteLine("Ссылки различаются");
while(true);
}
}
}
Результат выполнения примера
Значения одинаковые Ссылки различаются
Системные типы как классы имеют свои методы и свойства

Отметим удобные свойства и , которые содержат пределы поддерживаемого данным типом числового диапазона. Составим для иллюстрации небольшой тест для типа System.Double (псевдоним в C# - double )
using System;
namespace Test
{
class App
{
static void Main()
{
// Работаем как со структурным типом
System.Double dPI = System.Math.PI;
double dE = Math.E;
// Используем свойства и перегруженные методы
// структурного типа System.ValueType
Console.WriteLine("\tОбъект PI:");
Console.WriteLine("MinValue: \t{0}", System.Double.MinValue);
Console.WriteLine("MaxValue: \t{0}", System.Double.MaxValue);
Console.WriteLine("String Value: \t{0}", dPI.ToString());
Console.WriteLine("Value: \t\t{0}", dPI);
Console.WriteLine("TypeName: \t{0}", dPI.GetType().ToString());
Console.WriteLine("\n\tОбъект E:");
Console.WriteLine("MinValue: \t{0}", double.MinValue);
Console.WriteLine("MaxValue: \t{0}", double.MaxValue);
Console.WriteLine("String Value: \t{0}", dE.ToString());
Console.WriteLine("Value: \t\t{0}", dE);
Console.WriteLine("TypeName: \t{0}", dE.GetType().ToString());
while(true);
}
}
}
Результат выполнения примера
Объект PI: MinValue: -1,79769313486232E+308 MaxValue: 1,79769313486232E+308 String Value: 3,14159265358979 Value: 3,14159265358979 TypeName: System.Double Объект E: MinValue: -1,79769313486232E+308 MaxValue: 1,79769313486232E+308 String Value: 2,71828182845905 Value: 2,71828182845905 TypeName: System.Double
Следует отметить, что логический тип System.Boolean (или псевдоним bool ) ожидают только логические значения true или false. Никакие числовые значения им присваивать нельзя. Для текстовых данных в C# используются только два типа: string и char. Никакие другие разновидности наподобие char*, wchar_t*, LPSTR, LPCSTR, BSTR, OLECHAR и другие в C# не допустимы. Тип char описывает только одиночный символ Unicode (char symbol = 'П';), тип string описывает строку (набор) символов Unicode любой длины.
Если создаются переменные встроенных типов внутри класса (а не внутри метода класса), то компилятор при создании экземпляра сразу инициализирует этот экземпляр подходящим значением по умолчанию. Запустите следующий код в режиме отладки Debug, предварительно поставив точку останова на последнем операторе функции Main().
using System;
namespace Test
{
class DefValObject
{// Переменные встроенных типов создаются внутри класса
sbyte theSByte;
byte theByte;
short theShort;
ushort theUShort;
int theInt;
uint theUInt;
long theLong;
ulong theULong;
char theChar;
float theFloat;
double theDouble;
bool theBool;
decimal theDecimal;
string theString;
object theObject;
}
class App
{
static void Main()
{
DefValObject val = new DefValObject();
while(true); // Здесь поставим точку останова для отладчика
}
}
}
После останова программы в точке останова выполните последовательность нажатий клавиш < Ctrl+Alt+V, L >, чтобы вызвать окно Locals оболочки. Мы видим, что данные встроенных типов после создания объекта инициализируются.

Но если переменные встроенных типов создаются внутри какого-нибудь метода класса, то выполняется точно такая же автоматическая инициализация этих переменных.
Проверим это на подобном примере, только определения переменных перенесем внутрь метода класса.
using System;
namespace Test
{
class DefValObject
{
public void MyMethod() // Переменные внутри метода
{
sbyte theSByte;
byte theByte;
short theShort;
ushort theUShort;
int theInt;
uint theUInt;
long theLong;
ulong theULong;
char theChar;
float theFloat;
double theDouble;
bool theBool;
decimal theDecimal;
string theString;
object theObject;
return;// Здесь поставим точку останова для отладчика
}
}
class App
{
static void Main()
{
DefValObject val = new DefValObject();
val.MyMethod();
}
}
}
Константы определяют в виде переменных с ключевым словом const. Это заставляет компилятор следить за тем, чтобы программист случайно не попытался переприсвоить им новые значения. Константы можно определять и на уровне методов, но гораздо чаще они используются на уровне определений классов.
using System;
namespace Test
{
class DefConst
{
// Определение констант на уровне класса
private const int myIntConst = 5;
private const string myStringConst =
"Это константа уровня класса";
public void MyMethod()
{
// Определение констант на уровне метода
const int myIntConst = 10;
const string myStringConst =
"Это локальная константа метода";
Console.WriteLine("Константа класса: {0}",
DefConst.myIntConst);
Console.WriteLine("Константа класса: {0}",
DefConst.myStringConst);
Console.WriteLine("\n");
Console.WriteLine("Константа метода: {0}", myIntConst);
Console.WriteLine("Константа метода: {0}", myStringConst);
}
}
class App
{
static void Main()
{
DefConst instance = new DefConst();
instance.MyMethod();
while(true);
}
}
}
Результат выполнения примера
Константа класса: 5 Константа класса: Это константа уровня класса Константа метода: 10 Константа метода: Это локальная константа метода
Иногда удобно использовать служебный класс, созданный с единственной целью - служить в качестве хранилища констант. При этом нелишним будет позаботиться о том, чтобы случайно не создать экземпляр этого класса. Чтобы запретить создавать экземпляры класса, можно использовать любой из двух способов
Способ 1. Достаточно объявить конструктор класса как private
using System;
namespace Test
{
class DefConst
{
// Определение констант на уровне класса
public const int myIntConst = 5;
public const string myStringConst = "Это константа уровня класса";
private DefConst(){}
}
class App
{
static void Main()
{
//Error DefConst instance = new DefConst();
Console.WriteLine("Константа класса: {0}",
DefConst.myIntConst);
Console.WriteLine("Константа класса: {0}",
DefConst.myStringConst);
//Error Console.WriteLine("Константа класса: {0}", myIntConst);
//Error Console.WriteLine("Константа класса: {0}", myStringConst);
while(true);
}
}
}
Результат выполнения примера
Константа класса: 5 Константа класса: Это константа уровня класса
Способ 2. Достаточно объявить весь класс как абстрактный. Экземпляры абстрактных классов создавать нельзя (компилятор проследит за этим)
using System;
namespace Test
{
abstract class DefConst
{
// Определение констант на уровне класса
public const int myIntConst = 5;
public const string myStringConst =
"Это константа уровня класса";
//Конструктор вообще не нужен
}
class App
{
static void Main()
{
//Error DefConst instance = new DefConst();
Console.WriteLine("Константа класса: {0}",
DefConst.myIntConst);
Console.WriteLine("Константа класса: {0}",
DefConst.myStringConst);
//Error Console.WriteLine("Константа класса: {0}", myIntConst);
//Error Console.WriteLine("Константа класса: {0}", myStringConst);
while(true);
}
}
}
Результат выполнения примера
Константа класса: 5 Константа класса: Это константа уровня класса
Использование отдельного класса для хранения констант удобно тем, что C# не позволяет объявлять глобальные константы.
Ключевое слово const нельзя использовать как часть объявления метода. Для этих целей служат модификаторы in и out.
Мы знаем, что ссылочный тип объекта позволяет компактно передавать информацию об объекте любой функции, но все изменения, вносимые функцией, немедленно влияют на сам объект. В ссылочном виде в стеке хранится только ссылка на объект, а сам объект находится в управляемой куче оперативной памяти. В структурном виде в стеке хранится сам объект и при передаче функции передается копия объекта. Функция изменяет только копию переданного объекта, что никак не отражается на самом объекте.
Иногда может возникнуть необходимость передать в функцию ссылку, но внутри функции выполнить преобразование объекта в структурную форму. Такой процесс называется распаковкой. Создание ссылки на структурный объект называется упаковкой. Но следует иметь ввиду, что структурный объект всегда локальный на стеке, он разрушается при выходе из области видимости. Поэтому может оказаться, что ссылка будет ссылаться на уже несуществующий объект.
Приведем пример, где структурный объект упаковывается перед передачей в функцию, затем в ней распаковывается и используется. При распаковке создается локальная самостоятельная копия структурного объекта.
using System;
namespace Test
{
//*********************************************************************
struct PERSON // Структурный тип
{
// Данные класса без оператора доступа считаются private
string Name;// Прямая инициализация в структурах не допускается
int Age;// а в классах допускается
//****************************************
// Конструктор
//****************************************
public PERSON(string name, int age)
{
Name = name; Age = age;
}
//****************************************
// Сервисы
//****************************************
public string GetName()
{
return Name;
}
//****************************************
public int GetAge()
{
return Age;
}
//****************************************
public void SetAge(int age)
{
Age = age;
}
}
//*********************************************************************
class App
{
static void Main()
{
// Создаем объект на стеке
PERSON petrov = new PERSON("Денис
Петров", 20);
// Вывод
Console.WriteLine("Объект petrov: Имя={0};
возраст={1}",petrov.GetName(), petrov.GetAge());
// Упаковываем в ссылку для передачи куда-то
object pack = petrov;
PutClass.Put(pack);
while(true);
}
}
//*********************************************************************
class PutClass
{
public static void Put(object pack)
{
// В месте получения распаковываем в самостоятельную копию
PERSON unPack = (PERSON)pack;
// Изменяем копию для проверки, что это действительно копия
unPack.SetAge(33);
Console.WriteLine("Объект unPack: Имя={0};
возраст={1}", unPack.GetName(), unPack.GetAge());
}
}
}
Результат выполнения примера
Объект petrov: Имя=Денис Петров; возраст=20 Объект unPack: Имя=Денис Петров; возраст=33
Для организации ввода-вывода в C# предусмотрен класс System.Console. Он же предназначен для вывода сообщений об ошибках. Естественно, что этот класс чаще применяется в консольных приложениях. Системную консоль иногда называют командной строкой.
Основными методами класса являются WriteLine() и Write(). В отличие от метода Write() метод WriteLine() добавляет в конец строки символ новой строки \n автоматически. Метод ReadLine() позволяет считывать информацию с \n, метод Read() считывает с
Создайте консольное приложение с именем Test
Измените имя исходного файла на App.cs
Измените пространство имен на Test, а имя класса на BasicIO
Уберите аргументы у функции Main()
Заполните метод Main() следующим кодомusing System;
namespace Test
{
class BasicIO
{
static void Main()
{
// Тестируем пользователя
string name;
Console.Write("Введите Ваше имя: ");
name = Console.ReadLine(); // Читаем ввод после нажатия Enter
string age;
Console.Write("Введите Ваш возраст (лет): ");
age = Console.ReadLine(); // Читаем ввод после нажатия Enter
// Выводим полученную информацию на консоль
Console.WriteLine("\nПривет, дорогой {0}!
Вам {1} лет.", name, age);
while(true);
}
}
}
Результаты экрана
Введите Ваше имя: Снетков В.М. Введите Ваш возраст (лет): 55 Привет, дорогой Снетков В.М.! Вам 55 лет.
Нажмите комбинацию клавиш Ctrl-C или Ctrl-Break, чтобы прервать работу приложения, зацикленного на бесконечном выполнении предложения while(true);В C++ в консольном выводе функции prinf() применяются спецификаторы форматирования %d, %s, %c и т.д. в отдельности для каждого C# в консольном выводе функций Console.Write() и Console.Writeln() применяются универсальные индексированные метки {0}, {1}, {2}, и т.д., содержащие порядковый номер выводимого параметра в списке параметров. Эти подстановочные выражения обозначают
Параметры форматирования можно применять и для массивов. При этом подстановочные выражения будут указывать на элементы этого массива.
В подстановочном выражении можно указывать и более точные настройки формата с помощью дополнительных параметров форматирования. Параметры форматирования приведены в таблице, причем их можно указывать как строчными так и прописными буквами.
| Параметр | Значение |
|---|---|
C, c |
Используется для вывода значений в денежном ( Currency ) формате. По умолчанию перед выводимым значением подставляется символ доллара ($), хотя можно отменить подстановку этого символа при помощи объекта NumberFormatInfo. |
D, d |
Используется для вывода целых десятичных значений ( Decimal ). После этого символа можно указать количество значащих цифр. |
E, e |
Для вывода значений в экспоненциальном формате ( Scientific ). |
F, f |
Вывод значений с фиксированной точкой ( Fixed-point ). |
G, g |
Общий ( General ) формат.Применяется для вывода значений с фиксированной точностью или в экспоненциальном формате. |
N, n |
Стандартное числовое форматирование ( Number ) с использованием разделителей (пробелов или запятых) между разрядами. |
X, x |
Вывод значений в шестнадцатеричном формате ( Hexadecimal ). Если использовать заглавную X, то символы в шестнадцатеричном формате также будут заглавными. |
R, r |
Округление ( Round-trip ) |
Символы форматирования следуют в индексированных знакоместах сразу за номером подставляемого параметра через двоеточие:
{0:C}, {1:d}, {2:X} и т.д.
Параметры форматирования можно использовать для формирования любых символьных строк с использованием статического метода String.Format().
Измените консольное приложение с именем Test, заполнив метод Main() следующим кодомusing System;
namespace Test
{
class BasicIO
{
static void Main()
{
// Форматирование без параметров
string theString = "Привет всем!";
int theInt = 15;
float theFloat = 9.99F;
double theDouble = 123.456789;
BasicIO theClass = new BasicIO();
// Вывод
Console.WriteLine("Без параметров форматирования:");
Console.WriteLine("string: {0}\nint: {1}\nfloat:
{2}\ndouble: {3}\nobject: {4}"
, theString, theInt, theFloat, theDouble, theClass);
Console.Write("\n\n");
// Форматирование массива
object[] array = {"Привет!", 20.9, 1,
"55", Math.PI};
Console.WriteLine("Элементы массива:\n{0}; {1};
{2}; {3}; {4}", array);
Console.Write("\n\n");
// Форматирование с параметрами
Console.WriteLine("С параметрами форматирования:");
Console.WriteLine("C format: {0:C}", 99989.987);
Console.WriteLine("D9 format: {0:D9}", 99999);
Console.WriteLine("E format: {0:E}", .31415926538 * 10);
Console.WriteLine("F format: {0:F3}", 55555.6666);
Console.WriteLine("N format: {0:N}", 99999);
Console.WriteLine("X format: {0:X}", 99999);
Console.WriteLine("x format: {0:x}", 99999);
Console.Write("\n\n");
// Предварительное форматирование в символьную строку
string str;
str = String.Format("C format: {0:C}", 99989.987);
Console.WriteLine("Предварительное
форматирование в символьную строку:");
Console.WriteLine(str);
// Аналогично, с предварительным форматированием...
str = "ФИО: {{ {0} }}, Возраст: {{ {1} }}";
str = String.Format(str, "Снетков В.М.", 55);
Console.WriteLine("\n\nПредварительное
форматирование в символьную строку:");
Console.WriteLine(str);
while(true);
}
}
}
Результат форматирования вывода
Без параметров форматирования:
string: Привет всем!
int: 15
float: 9,99
double: 123,456789
object: Test.BasicIO
Элементы массива:
Привет!; 20,9; 1; 55; 3,14159265358979
С параметрами форматирования:
C format: 99 989,99р.
D9 format: 000099999
E format: 3,141593E+000
F format: 55555,667
N format: 99 999,00
X format: 1869F
x format: 1869f
Предварительное форматирование в символьную строку:
C format: 99 989,99р.
Предварительное форматирование в символьную строку:
ФИО: { Снетков В.М. }, Возраст: { 55 }
Метод - это способ упаковки кода, который рассматривается как единое целое и может быть выполнен в процессе работы программы. В C# не существует глобальных методов - любой метод обязательно должен быть членом класса или структуры.
Методы могут принимать или не принимать параметры, могут возвращать или не возвращать значения, могут быть методами класса (статическими) или
Для каждого метода существует свой уровень доступа, который определяет, откуда можно будет обратиться к этому методу. Для указания уровня доступа существуют специальные модификаторы. Они приведены в таблице
| Модификатор | Назначение |
|---|---|
public |
Модификатор общедоступности метода |
private (по умолчанию) |
Метод будет доступен только внутри класса, где он определен. Если при объявлении метода модификатор явно не указан, то используется по умолчанию модификатор private |
protected |
Метод будет доступен только внутри самого класса и внутри любого производного от него класса. Для остальных вызовов из внешнего мира такой метод будет недоступен |
internal |
Метод будет доступен из всех классов внутри сборки, в которой он определен. Из-за пределов этой сборки обратиться к нему будет нельзя |
protected internal |
Доступен для самого класса и его наследников внутри сборки, где они определены |
Ниже приведены заготовки кода, иллюстрирующие уровни доступа к методам.
// Уровни доступа к методам
class SomeClass
{
// Доступен отовсюду
public void Method1(){...}
// Доступен только из методов класса SomeClass
private void Method2(){...}
// Доступен из класса SomeClass и его наследников
// на любом нижестоящем уровне иерархии наследования
protected void Method3(){...}
// Доступен из всех классов той же сборки
internal void Method4(){...}
// Доступен для себя и наследников из той же сборки
protected internal void Method5(){...}
// Только для внутреннего пользования
void Method6(){...}
}
Сейчас мы продемонстрируем то, что в C# имена классов, имена пространства имен, идентификаторы типов данных и имена методов можно писать русскими буквами. Только ключевые слова должны оставаться неизменными, а все идентификаторы, которые волен выбирать программист, можно излагать кириллицей. Мы просто это продемонстирируем и заметим, что это плохая практика, хотя такая возможность есть.
using System;
namespace МоеПространствоИмен
{
class Жалобщик
{
// Объект для генерации случайных чисел
// rand - закрытая private переменная класса
System.Random rand = new System.Random();
private int ДатьЧисло(short лимит)
{
// Генерирует целое число в диапазоне
// от нуля до "лимит"
return rand.Next(лимит);
}
public string ГенераторЖалоб()
{
string[] массивЖалоб = new string[5]
{
"О, зачем я пошел на эту специальность!",
"Как бы я был счастлив, если бы не C#!",
"Ну почему именно я и именно со мной!",
"Подвесить бы Уайта Гейтца за его язык C#!",
"Единственное спасение - уволить Снеткова!"
};
return массивЖалоб[ДатьЧисло(5)];
}
static void Main()
{
Жалобщик жалобы = new Жалобщик();
for(int i = 0; i < 10; i++)
{
Console.WriteLine("{0}) \t{1}",
i+1, жалобы.ГенераторЖалоб());
}
while(true);
}
}
}
Результаты выполнения не зависят от "культуры"
1) О, зачем я пошел на эту специальность! 2) Как бы я был счастлив, если бы не C#! 3) Единственное спасение - уволить Снеткова! 4) Как бы я был счастлив, если бы не C#! 5) Как бы я был счастлив, если бы не C#! 6) Подвесить бы Уайта Гейтца за его язык C#! 7) О, зачем я пошел на эту специальность! 8) Как бы я был счастлив, если бы не C#! 9) Единственное спасение - уволить Снеткова! 10) Подвесить бы Уайта Гейтца за его язык C#!
Предостережение
Никогда не используйте русские имена идентификаторов в программе. Иначе мозги лопнут если и не у Вас, так у бедного сопровождающего программиста - точно.
(методы уровня классов и методы уровня объектов; общие данные для всех объектов и индивидуальные данные для каждого объекта))
Если метод объявлен с ключевым словом static, то его нужно вызывать с именем самого класса. Более того, если статический метод для внутренних целей использует другие методы-члены и данные-члены класса, то они также должны быть объявлены в классе статическими. При этом статические методы уже нельзя будет вызывать применительно к экземплярам класса.
Если же ключевое слово static при объявлении метода опущено, то метод считается методом уровня объекта и его можно вызывать только с именем объекта. При этом будут обрабатываться данные, принадлежащие конкретному объекту как экземпляру класса.
Классы в C# могут содержать не только статические методы, но и статические данные-члены (
Приведем примеры...
using System;
namespace Test
{
class Teenager
{
// Объект для генерации случайных чисел
// rand - закрытая private переменная класса
static System.Random rand = new System.Random();
private static int GetRandomNumber(short upperLimit)
{
// Генерирует целое число в диапазоне
// от 0 до upperLimit
return rand.Next(upperLimit);
}
public static string Complain()
{
string[] messages = new string[5]
{
"О, зачем я пошел на эту специальность!",
"Как бы я был счастлив, если бы не C#!",
"Ну почему именно я и именно со мной!",
"Подвесить бы Уайта Гейтца за его язык C#!",
"Единственное спасение - уволить Снеткова!"
};
return messages[GetRandomNumber(5)];
}
static void Main()
{
for(int i = 0; i < 10; i++)
{
Console.WriteLine("{0}) \t{1}", i+1,
Teenager.Complain());
}
while(true);
}
}
}
Результаты выполнения
1) Подвесить бы Уайта Гейтца за его язык C#! 2) О, зачем я пошел на эту специальность! 3) Как бы я был счастлив, если бы не C#! 4) О, зачем я пошел на эту специальность! 5) О, зачем я пошел на эту специальность! 6) Ну почему именно я и именно со мной! 7) Ну почему именно я и именно со мной! 8) Как бы я был счастлив, если бы не C#! 9) Подвесить бы Уайта Гейтца за его язык C#! 10) Ну почему именно я и именно со мной!
Обратите внимание, что один из статических методов GetRandomNumber() мы скрыли - он нужен только для внутренних целей.
using System;
namespace Test
{
class Teenager
{
// Объект для генерации случайных чисел
// rand - закрытая private переменная класса
System.Random rand = new System.Random();
private int GetRandomNumber(short upperLimit)
{
// Генерирует целое число в диапазоне
// от 0 до upperLimit
return rand.Next(upperLimit);
}
public string Complain()
{
string[] messages = new string[5]
{
"О, зачем я пошел на эту специальность!",
"Как бы я был счастлив, если бы не C#!",
"Ну почему именно я и именно со мной!",
"Подвесить бы Уайта Гейтца за его язык C#!",
"Единственное спасение - уволить Снеткова!"
};
return messages[GetRandomNumber(5)];
}
static void Main()
{
Teenager boy = new Teenager();
for(int i = 0; i < 10; i++)
{
Console.WriteLine("{0}) \t{1}", i+1, boy.Complain());
}
while(true);
}
}
}
Результаты выполнения
1) Ну почему именно я и именно со мной! 2) Единственное спасение - уволить Снеткова! 3) Ну почему именно я и именно со мной! 4) Подвесить бы Уайта Гейтца за его язык C#! 5) Подвесить бы Уайта Гейтца за его язык C#! 6) Единственное спасение - уволить Снеткова! 7) Подвесить бы Уайта Гейтца за его язык C#! 8) О, зачем я пошел на эту специальность! 9) О, зачем я пошел на эту специальность! 10) Единственное спасение - уволить Снеткова!
Конструктор класса не может быть статическим, как и не должен возвращать никакого значения.
using System;
namespace Test
{
class CountObject
{
private static int countObject = 0;
public CountObject()// Конструктор
{
countObject++;
}
public int GetCountFromObject()
{
return countObject;
}
public static int GetCountFromClass()
{
return countObject;
}
static void Main()
{
CountObject ob1 = new CountObject();
CountObject ob2 = new CountObject();
CountObject ob3 = new CountObject();
CountObject ob4 = new CountObject();
CountObject ob5 = new CountObject();
Console.WriteLine("Создано объектов: {0}",
ob1.GetCountFromObject());
Console.WriteLine("Создано объектов: {0}",
ob2.GetCountFromObject());
Console.WriteLine("Создано объектов: {0}",
ob3.GetCountFromObject());
Console.WriteLine("Создано объектов: {0}",
ob4.GetCountFromObject());
Console.WriteLine("Создано объектов: {0}",
ob5.GetCountFromObject());
Console.WriteLine("\nСоздано объектов: {0}",
CountObject.GetCountFromClass());
while(true);
}
}
}
Результат примера
Создано объектов: 5 Создано объектов: 5 Создано объектов: 5 Создано объектов: 5 Создано объектов: 5 Создано объектов: 5
В классе System.Environment определено множество инструментов, в том числе
using System;
namespace Test
{
class Test
{
static void Main()
{
// Под какой OC мы работаем?
Console.WriteLine("Текущая OC: \t\t{0}",
Environment.OSVersion);
// Текущий каталог?
Console.WriteLine("Текущий каталог: \t{0}",
Environment.CurrentDirectory);
// Список логических дисков
Console.WriteLine("Список дисков:");
string[] drives = Environment.GetLogicalDrives();
for(int i = 0; i < drives.Length; i++)
Console.WriteLine("Диск {0}) \t\t{1}",
i+1, drives[i]);
// Какая версия платформы .NET у нас установлена?
Console.WriteLine("Текущая версия .NET: \t{0}",
Environment.Version);
while(true);
}
}
}
Результаты выполнения примера
Текущая OC: Microsoft Windows NT 5.0.2195.0 Текущий каталог: C:\Test\bin\Release Список логических дисков: Диск 1) A:\ Диск 2) C:\ Диск 3) D:\ Диск 4) E:\ Диск 5) F:\ Диск 6) Z:\ Текущая версия .NET: 1.1.4322.573
Программист может поручить компилятору контроль за параметрами методов, обозначив одни как входные, другие - как выходные. Для таких целей в C# предусмотрен ряд модификаторов, перечисленных в таблице
| Модификатор | Назначение |
|---|---|
(нет) - по умолчанию in ) |
Если модификатор никак не помечен, то по умолчанию считается, что это входящий параметр in для передачи переменной как значения. Вместо пропуска можно поставить in - результат будет таким же. |
out |
Маркирует возвращаемый методом параметр. |
ref |
Маркирует входной параметр, передаваемый по ссылке. Изменение параметра внутри метода означает изменение самих данных, хранящихся вне метода и адресуемых ссылкой. |
params |
Этот модификатор позволяет передавать целый набор параметров как единое целое. В любом методе может быть только один модификатор params и параметр с этим модификатором должен стоять последним в списке параметров метода. |
Проиллюстрируем прменение модификаторов на примере
using System;
namespace Test
{
class Test
{
//***************************************
public int Add(int x, int y)
{
return x + y;
}
//***************************************
public void Add(int x, int y, out int sum)
{
sum = x + y;
}
//***************************************
public void UpperCaseThisString(ref string str)
{
// Возвращает символьную строку с верхнем регистре
str = str.ToUpper();
}
//***************************************
public void DisplayArrayOfInt(string msg, params int[] list)
{
Console.Write(msg);
for(int i = 0; i < list.Length; i++)
Console.Write(" {0}", list[i]);// Вывод в строку
}
//***************************************
//***************************************
static void Main()
{
Test test = new Test();
Console.WriteLine("5 + 10 = {0}", test.Add(5, 10));
int sum; // Инициализировать необязательно - выходной
test.Add(50, 100, out sum);
Console.WriteLine("50 + 100 = {0}", sum);
string message = "\nПривет студентам от Снеткова!";
Console.WriteLine("Перед обработкой:{0}", message);
test.UpperCaseThisString(ref message);
Console.WriteLine("После обработки:{0}", message);
int[] arrayInt = new int[3]{1, 2, 3};
test.DisplayArrayOfInt("Вывод из массива целых:",
arrayInt);
test.DisplayArrayOfInt("\nВывод списка из трех целых:",
11, 22, 33);
test.DisplayArrayOfInt("\nВывод списка из пяти целых:",
55, 66, 77, 88, 99);
while(true);
}
}
}
Результат примера
5 + 10 = 15 50 + 100 = 150 Перед обработкой: Привет студентам от Снеткова! После обработки: ПРИВЕТ СТУДЕНТАМ ОТ СНЕТКОВА! Вывод из массива целых: 1 2 3 Вывод списка из трех целых: 11 22 33 Вывод списка из пяти целых: 55 66 77 88 99
Отметьте для себя, что модификаторы out и ref должны применяться и в определении метода и в его вызове, т.е. как при описании формальных параметров, так и фактических.
Можно передавать в качестве множественных параметров любые типы данных, если определить в списке
using System;
namespace Test
{
class Person
{
private string Name;
private int Age;
//***************************************
// Конструктор
public Person(string name, int age)
{
Name = name;
Age = age;
}
//***************************************
// Конструктор по умолчанию
public Person(){}
//***************************************
public void PrintInfo()
{
Console.WriteLine("Имя={0}; Возраст={1}", Name, Age);
}
}
//****************************************************************
class Test
{
//***************************************
// Что же мне пришлют на этот раз?
public void DisplayArrayOfObject(params object[] list)
{
for(int i = 0; i < list.Length; i++)
{
// Относится ли текущий объект из переданного
// массива объектов к классу Person?
if(list[i] is Person)
((Person)list[i]).PrintInfo();
else
Console.WriteLine(list[i]);
}
}
//***************************************
static void Main()
{
// Создали объект только
// для того, чтобы вызвать его метод
Test test = new Test();
// Создаем сотрудника
Person person = new Person("Студент
Вильгельм И.А.", 21);
// Передаем сотрудника и другие разнотипные объекты в нужном
// количестве методу, объявленному с модификатором params
test.DisplayArrayOfObject(
"Переменное число параметров любого типа",
"T 001 TT",
12345,
person,
Math.PI,
true,
"Привет студентам от Снеткова",
"..................... и т.д.");
while(true);
}
}
}
Результат примера
Переменное число параметров любого типа T 001 TT 12345 Имя=Студент Вильгельм И.А.; Возраст=21 3,14159265358979 True Привет студентам от Снеткова ..................... и т.д.
В языке C# нет операций адресации и разадресации и *, как это имеет место быть в C и C++. Вся передача обеспечивается использованием ссылок с явным или неявным использованием модификатора ref.
Массивы в C# - это набор однотипных элементов, доступ к которым производится по их числовому индексу. Наряду с массивами элементов простого встроенного типа (имеющим псевдонимы системных типов) существуют
Следует помнить, что размер массива задается при его создании, но не при объявлении. Если мы создаем new обязательно. Если при объявлении мы массив сразу инициализируем, то new необязательно - размерность массива определит компилятор. Индексация элементов массива начинается с нуля.
В C++ список инициализации может по количеству элементов не соответствовать заказанной C# заказанная
using System;
namespace Test
{
class Test
{
//***************************************
private void DisplayString(string subTitle, string[] list)
{
Console.WriteLine(subTitle);
for(int i = 0; i < list.Length; i++)
{
Console.WriteLine("{0}) {1}", i, list[i]);
}
}
//***************************************
private void DisplayInt(string subTitle, int[] list)
{
Console.WriteLine(subTitle);
for(int i = 0; i < list.Length; i++)
{
Console.WriteLine("{0}) {1}", i, list[i]);
}
}
//***************************************
static void Main()
{
string title = "************************
********************\n"
+ "\tОдномерные массивы:\n"
+ "*****************************
***************\n";
Console.WriteLine(title);
// Создадим объект для применения методов
// DisplayString() и DisplayInt()
Test test = new Test();
// Создадим ссылочную переменную типа массива с адресом null
string[] str1;
// Запросим память у системы для 10 элементов и запомним адрес.
str1 = new string[10];
test.DisplayString( "Строковый массив без
инициализации элементов.\n"
+ "Строковые элементы по умолчанию\n"
+ "инициализируются пустыми строками."
, str1);
// Поэлементная инициализация с использованием индексов
// и той же ссылки с новым адресом.
// Адрес старого массива потерян в куче. Утерянную
// память системе вернет сборщик мусора Garbare Collector
str1 = new string[7];
str1[0] = "Колян";
str1[1] = "Вован";
str1[2] = "Иван";
str1[3] = "Болван";
str1[4] = "Чурбан";
test.DisplayString("\nПоэлементная
инициализация с использованием индекса.\n"
+ "Старая адресуемая память утеряна
безвозвратно:", str1);
// Создадим ссылочную переменную и сразу инциализируем ее адресом.
int[] int1 = new int[5];
test.DisplayInt("\n"
+ "Числовой массив фиксированного размера.\n"
+ "Элементы по умолчанию инициализируются нулями."
, int1);
// Создаем ссылку на массив. Инициализируем ее адресом, запросив
// память с фиксированным количеством элементов,
// и сразу инициализируем элементы массива.
// Размер списка инициализации должен строго соответствовать
// запрошенной размерности массива.
int[] int2 = new int[5] {11, 22, 33, 44, 55};
test.DisplayInt(
"\n"
+ "Запрошенный размер массива должен\n"
+ "строго соответствовать списку инициализации."
, int2);
// Создаем ссылку на массив. Инициализируем ее адресом, запросив
// память без указания количества элементов. Количество элементов
// определит компилятор при инициализации списком.
// Используем оператор new.
int[] int3 = new int[] {11, 22, 33, 44, 55, 66, 77};
test.DisplayInt(
"\n"
+ "Размер по списку инициализации, \nпамять
запрашивается по new"
+ ":"
, int3);
// Создаем ссылку на массив. Инициализируем ее адресом, запросив
// память без указания количества элементов. Количество элементов
// определит компилятор при инициализации списком.
// Оператор new не используем.
int[] int4 = {11, 22, 33, 44, 55, 66, 77, 88, 99};
test.DisplayInt(
"\n"
+ "Размер по списку инициализации, \nпамять
запрашивается без new"
+ ":"
, int4);
while(true);
}
}
}
Результат выполнения примера
******************************************** Одномерные массивы: ******************************************** Строковый массив без инициализации элементов. Строковые элементы по умолчанию инициализируются пустыми строками. 0) 1) 2) 3) 4) 5) 6) 7) 8) 9) Поэлементная инициализация с использованием индекса. Старая адресуемая память утеряна безвозвратно: 0) Колян 1) Вован 2) Иван 3) Болван 4) Чурбан 5) 6) Числовой массив фиксированного размера. Элементы по умолчанию инициализируются нулями. 0) 0 1) 0 2) 0 3) 0 4) 0 Запрошенный размер массива должен строго соответствовать списку инициализации. 0) 11 1) 22 2) 33 3) 44 4) 55 Размер по списку инициализации, память запрашивается по new: 0) 11 1) 22 2) 33 3) 44 4) 55 5) 66 6) 77 Размер по списку инициализации, память запрашивается без new: 0) 11 1) 22 2) 33 3) 44 4) 55 5) 66 6) 77 7) 88 8) 99
Многомерные массивы могут иметь любое количество измерений (не путать с N измерений - N _мерные. Наибольшее распространение получили массивы, имеющие два измерения - двумерные многомерные массивы. Многомерные массивы (в том числе двумерные) бывают двух типов:
Ниже рассмотрим примеры, касающиеся только двумерных массивов.
using System;
namespace Test
{
class Test
{
static void Main()
{
string title = "*****************************
***************\n"
+ "\tДвумерные массивы:\n"
+ "****************************
****************\n";
Console.WriteLine(title);
//*********************************************************
// Создаем ссылку типа int на двумерный массив
// Используются для измерений общие скобки
int[,] array1;
// Запросим память у системы для
// Для фиксированного количества элементов
array1 = new int[6,5];
Console.WriteLine("Прямоугольный массив [6,5],
полученный по\n"
+ "синтаксису массива прямоугольного типа:");
// Заполним и выведем массив с поэлементной индексацией
// Метод GetLength() используется для определения
// количества элементов в указанном измерении массива
for(int i = 0; i < array1.GetLength(0); i++)
{
for(int j = 0; j < array1.GetLength(1); j++)
{
array1[i,j] = i * j;
Console.Write(array1[i,j] + "\t");
}
Console.WriteLine();// На новую строку экрана
}
//*********************************************************
// Используются для измерений индивидуальные скобки
int[][] array2;
// Запросим память у системы для
// Для неопределенного количества элементов
// по второму измерению
array2 = new int[6][];
Console.WriteLine("\n\nПрямоугольный массив [6][5],
полученный по\n"
+ "синтаксису массива ломаного типа:");
// Вначале запросим память для второго измерения
// Используются для измерения индивидуальные скобки
for(int i = 0; i < array2.Length; i++)
array2[i] = new int[5];
// Заполним и выведем массив с поэлементной индексацией
for(int i = 0; i < 6; i++)
{
for(int j = 0; j < 5; j++)
{
array2[i][j] = i * j;
Console.Write(array2[i][j] + "\t");
}
Console.WriteLine();// На новую строку экрана
}
//*********************************************************
// Используются для измерений индивидуальные скобки
// Запросим память у системы для
// Для неопределенного количества элементов
// по второму измерению
array2 = new int[6][]; // Используем старую ссылку
// массива ломаного типа, старую ссылку
// массива прямоугольного типа применять нельзя
Console.WriteLine("\n\nМассив ломаного
типа [6][]:");
// Вначале запросим память для второго измерения
// Используются для измерения индивидуальные скобки
for(int i = 0; i < array2.Length; i++)
array2[i] = new int[i + 2];
// Заполним и выведем массив с поэлементной индексацией
for(int i = 0; i < array2.Length; i++)
{
for(int j = 0; j < array2[i].Length; j++)
{
array2[i][j] = i * j;
Console.Write(array2[i][j] + "\t");
}
Console.WriteLine();// На новую строку экрана
}
while(true);
}
}
}
Результат выполнения примера
******************************************** Двумерные массивы: ******************************************** Прямоугольный массив [6,5], полученный по синтаксису массива прямоугольного типа: 0 0 0 0 0 0 1 2 3 4 0 2 4 6 8 0 3 6 9 12 0 4 8 12 16 0 5 10 15 20 Прямоугольный массив [6][5], полученный по синтаксису массива ломаного типа: 0 0 0 0 0 0 1 2 3 4 0 2 4 6 8 0 3 6 9 12 0 4 8 12 16 0 5 10 15 20 Массив ломаного типа [6][]: 0 0 0 1 2 0 2 4 6 0 3 6 9 12 0 4 8 12 16 20 0 5 10 15 20 25 30
Все массивы в C# являются производными от базового класса System.Array. За счет этого любой массив наследует множество методов и свойств, которые сильно упрощают работу программиста. Методы можно увидеть в суфлере кода IntelliSense, применив его к имени класса или конкретному экземпляру.
![]() |
![]() |
| Член класса | Назначение |
|---|---|
|
Этот статический метод можно использовать только тогда, когда массив реализует интерфейс ICompare. Если этот интерфейс реализован, то данный метод позволяет найти элемент массива. |
Clear() |
Этот статический метод позволяет очистить указанный диапазон элементов и инициализировать их "безопасными" значениями. Массив целых получит нулевые значения элементов, строковый - пустые строки, ссылки преобретут значения null. |
CopyTo() |
Используется для копирования элементов из исходного массива в массив назначения. |
GetEnumerator() |
Возвращает интерфейс для указанного массива. Этот интерфейс необходим для применения конструкции цикла foreach. |
GetLength(), Length |
Метод GetLength() возвращает количество элементов для заданного в качестве аргумента измерения массива. Нумерация измерений начинается с нуля. Length - это свойство только для чтения, содержащее общее количество элементов массива. |
GetLowerBound(), GetUpperBound() |
Эти методы используются для определения нижней и верхней границ выбранного измерения массива. |
GetValue(), SetValue() |
Возвращает или устанавливает значение элемента по указанному в качестве аргумента индексу. Этот метод |
Reverse() |
Этот статический метод изменяет порядок следования элементов массива на противоположный. |
Sort() |
Сортирует одномерный массив IComparer, то с помощью этого метода можно сортировать и данные пользовательских типов. |
Рассмотрим примеры
using System;
namespace Test
{
class Test
{
private void DisplayString(string title, string[] list)
{
Console.WriteLine(title);
for(int i = 0; i < list.Length; i++)
Console.Write(list[i] + "\t");
Console.WriteLine(); // Для перевода строки
}
static void Main()
{
// Создадим объект для применения метода DisplayString()
Test test = new Test();
string title = "********************
************************\n"
+ "\tМетоды класса System.Array:\n"
+ "**************************
******************\n";
Console.WriteLine(title);
// Создаем строковый массив
string[] firstNames = new string[5]
{
"Колян",
"Вован",
"Иван",
"Болван",
"Чурбан"
};
test.DisplayString("Исходный массив:", firstNames);
// Расставим элементы в обратном порядке
// при помощи статического метода System.Array
Array.Reverse(firstNames);
test.DisplayString("\nИнверсный массив:
", firstNames);
// Сортируем элементы по алфавиту
// при помощи статического метода System.Array
Array.Sort(firstNames);
test.DisplayString("\nСортированный
массив:", firstNames);
// Очищаем элементы массива, начиная с имеющего
// индекс 1 три стоящих подряд
// при помощи статического метода System.Array
Array.Clear(firstNames, 1, 3);
test.DisplayString("\nЧастично очищенный массив:
", firstNames);
while(true);
}
}
}
Результат выполнения примера
******************************************** Методы класса System.Array: ******************************************** Исходный массив: Колян Вован Иван Болван Чурбан Инверсный массив: Чурбан Болван Иван Вован Колян Сортированный массив: Болван Вован Иван Колян Чурбан Частично очищенный массив: Болван Чурбан
Встроенный тип string (строки Unicode ) относится к System.String. Этот базовый класс обеспечивает программиста множеством полезных свойств и методов для обработки строк. Часть методов является методами объектов, часть - методами класса (статическими).
| Член класса | Назначение |
|---|---|
Length |
Это свойство возвращает длину указанной строки |
Concat() |
Этот статический метод возвращает новую строку, склеенную из двух исходных |
CompareTo() |
Сравнивает две строки |
Copy() |
Этот статический метод создает новую копию существующей строки |
Format() |
Используется для форматирования строки с использованием спецификаторов форматирования и подстановочных выражений вида {0} |
Insert() |
Используется для вставки строки внутрь существующей |
PadLeft() PadRight() |
Для заполнения строки слева или справа указанными символами |
Remove() Replace() |
Эти методы позволяют создать копию строки с внесенными изменениями (частично удаленными или замененными символами) |
ToUpper() ToLower() |
Возвращают копию строки с символами в верхнем или нижнем регистре |
Для этого типа некоторые операции перегружены так, что позволяют работать со значениями, а не со ссылками. Например, при использовании операторов равенства == или неравенства != происходит сравнение значений строковых объектов, а не адресов этих объектов в оперативной памяти. Оператор сложения + перегружен так, что при его применении к строковым объектам вызывается неявно метод Concat(). Кроме того, можно обратиться к любому символу строки с помощью оператора индекса [].
using System;
namespace Test
{
class Test
{
static void Main()
{
string title = "******************
**************************\n"
+ "\tМетоды класса System.String\n"
+ "************************
********************\n";
Console.WriteLine(title);
// Создаем строку по классу
System.String strObject = "Это строка-экземпляр
класса System.String:";
string strAlias = "Это строка-экземпляр
встроенного псевдонима string:";
// Сравниваем содержимое
if(strObject == strAlias)
Console.WriteLine("Строки равны");
else
Console.WriteLine("Строки неравны");
// Склеиваем строки
string addString = strObject + strAlias;
Console.WriteLine("\nСклеенные строки\n{0}",
addString);
// Применяем индексатор для доступа к символам строки
Console.WriteLine("\nСимволы строки \"
{0}\"", strAlias);
for(int i = 0; i < strAlias.Length; i++)
Console.WriteLine("{0}. {1}", i,
"\t" + strAlias[i]);
while(true);
}
}
}
Результат выполнения примера
******************************************** Методы класса System.String ******************************************** Строки неравны Склеенные строки Это строка-экземпляр класса System.String:Это строка-экземпляр встроенного псевдонима string: Символы строки "Это строка-экземпляр встроенного псевдонима string:" 0. Э 1. т 2. о 3. 4. с 5. т 6. р 7. о 8. к 9. а 10. - 11. э 12. к 13. з 14. е 15. м 16. п 17. л 18. я 19. р 20. 21. в 22. с 23. т 24. р 25. о 26. е 27. н 28. н 29. о 30. г 31. о 32. 33. п 34. с 35. е 36. в 37. д 38. о 39. н 40. и 41. м 42. а 43. 44. s 45. t 46. r 47. i 48. n 49. g 50. :
В C# строки могут содержать любое количество
| Управляющий символ | Назначение |
|---|---|
@ |
Префикс дословного вывода строк ( verbatim strings ). Ставится перед формируемой строкой и отменяет срабатывание всех управляющих символов |
\' |
Вставить одинарную кавычку |
\" |
Вставить двойную кавычку |
\\ |
Вставить обратный слэш |
{{ |
Вставить фигурную открывающую скобку |
}} |
Вставить фигурную закрывающую скобку |
\a |
Запустить системное звуковое оповещение ( Alert ) |
\b |
Вернуться на одну позицию ( Backspace ) |
\f |
Начать следующую страницу ( ) |
\n |
Вставить новую строку ( New line ) |
\r |
Вставить возврат каретки ( ) |
\t |
Вставить горизонатльный символ табуляции ( horizontal Tab ) |
\u |
Вставить символ Unicode |
\v |
Вставить вертикальный символ табуляции ( Vertical tab ) |
\o |
Представляет нулевой символ NULL (все биты представления нулевые) |
using System;
namespace Test
{
class Test
{
static void Main()
{
// Форматирование без параметров + Звукнем
string str = String.Format("\aЯ передаю \"
Привет всем!\" {{Снетков}}\n");
Console.WriteLine(str);
while(true);
}
}
}
Результат выполнения примера
Я передаю "Привет всем!" {Снетков}
При работе со строками в C# первоначальное значение строки не может быть изменено. Мы применяем к строкам множество модифицирующих методов и получаем нужный результат, но на самом деле методы каждое изменение строки записывают на новом месте в куче, а не используют одну и ту же область памяти. Ссылки на старые места хранения строк безжалостно бросаются, а методы возвращают лишь копии измененного содержимого, размещенного на новом месте. Брошенные ссылки подберет позже сборщик мусора, когда будет отдавать системе уже неадресуемые места памяти.
Когда потребуется не создавать лишние копии строк (особенно для больших объемов данных), а обрабатывать строки напрямую в месте их хранения, можно воспользоваться классом StringBuilder в пространстве имен System.Text.
Создание объекта класса StringBuilder осуществляется при помощи одного из многих его перегруженных конструкторов. Параметры конструкторов позволяют задавать значения четырех свойств, приведенных в таблице.
| Свойство | Назначение |
|---|---|
Capacity
(емкость) |
Свойство определяет исходный размер элемента символьного массива в экземпляре класса StringBuilder. По умолчанию принято значение 16. Если существующий размер меньше, чем необходимо для такой операции как добавление, то объект класса StringBuilder удваивает размер символьного массива. Поскольку такие операции снижают производительность, рекомендуется заранее задавать необходимый размер, если он известен на момент создания объекта класса StringBuilder. |
Length
(длина) |
Длина строки в данном экземпляре объекта класса StringBuilder. |
MaxCapacity
(максимальная емкость) |
Свойство задает максимальное количество символов, которое может содержать текущий экземпляр класса StringBuilder. По умолчанию принято значение Int32.. Свойство StringBuilder.MaxCapacity уже созданного объекта не может быть изменено. |
Chars
(символы) |
Это индексируемое свойство внутреннего символьного массива, входящего в объект StringBuilder. Индексы этого свойства начинаются с нуля. |
| Метод | Назначение |
|---|---|
Append() |
Добавляет строковое ( System.String ) представление указанного объекта к содержимому экземпляра класса StringBuilder |
AppendFormat() |
Подобен методу Append(), подразумевает применение спецификаторов форматирования и национальных особенностей. |
EnsureCapacity() |
Гарантирует определенную емкость символьного массива. |
Equals() |
Сранивает два экземпляра класса StringBuilder на совпадение значений всех свойств и содержимого символьного массива. |
Insert() |
Вставляет строковое представление указанного объекта в определенную позицию символьного массива экземпляра StringBuilder |
Remove() |
Удаляет определенный |
Replace() |
Заменяет в объекте указанные вхождения строк на другие |
ToString() |
Преобразует допускающий изменения экземпляр класса System.Text.StringBuilder в неизменный экземпляр класса System.String |
using System; // Для класса String
using System.Text; // Для класса StringBuilder
namespace Test
{
class Test
{
static void Main()
{
String strFixed = new String(
"Эта исходная строка не меняется".ToCharArray());
String strUpper = strFixed.ToUpper();
Console.WriteLine(strFixed);
Console.WriteLine(strUpper);// Новая копия
int capacity = 100; // Исходный размер
StringBuilder myBuffer = new StringBuilder(
"\nИсходная строка", capacity);
myBuffer.Append("+Добавленная строка");
Console.WriteLine(myBuffer);// Та же строка
Console.ReadLine();
}
}
}
Результат выполнения примера
Эта исходная строка не меняется ЭТА ИСХОДНАЯ СТРОКА НЕ МЕНЯЕТСЯ Исходная строка+Добавленная строка
using System;
namespace Test
{
enum EmpType
{
Manager,
Grunt,
Contractor,
VP
}
class Test
{
void Variant(EmpType en)
{
switch(en)
{
case EmpType.Contractor:
Console.WriteLine("Работает по контракту");
break;
case EmpType.Grunt:
Console.WriteLine("");
break;
case EmpType.Manager:
Console.WriteLine("");
break;
case EmpType.VP:
Console.WriteLine("");
break;
default:
Console.WriteLine("");
break;
}
}
static void Main()
{
Console.ReadLine();
}
}
}
Компания Microsoft специально для платформы .NET Framework разработала язык C# (Си-шарп). В отличие от языка C++, где основой программирования является упаковка кода в классы и функции, в C# введено более общее понятие - типы. Теперь типы включают в себя на равных классы, структуры, интерфейсы, перечисления и делегаты.
| abstract | as | base | bool | break | byte | case |
| catch | char | checked | class | const | continue | decimal |
| default | delegate | do | double | else | enum | event |
| explicit | extern | false | finally | fixed | float | for |
| foreach | goto | if | implicit | in | int | interface |
| internal | is | lock | long | namespace | new | null |
| object | operator | out | override | params | private | protected |
| public | readonly | ref | return | sbyte | sealed | short |
| sizeof | stackalloc | static | string | struct | switch | this |
| throw | true | try | typeof | uint | ulong | unchecked |
| unsafe | ushort | using | virtual | void | volatile | while |
Существование самостоятельных глобальных функций, как это имеет место в C++, отменено. Теперь разработка кода программы сводится к последовательному развитию типов, главным из которых по прежнему остается класс. Каждый класс рекомендуется размещать, как самостоятельный фрагмент кода программы, в отдельном файле с расширением *.cs. Класс не принято разбивать на две части: объявление (файл *.h ) и реализацию (файл *.cpp ) - все размещается в одном файле *.cs.
Внутри класса дается объявление и реализация методов, определяющих его функциональность. Каждый класс размещается внутри именованных скобок namespace Имя{...}, задающих простанство имен. Кроме того, вначале файла с пользовательским классом перечисляются пространства имен используемых библиотечных классов. Объявление класса не должно заканчиваться точкой_с_запятой, как это принято в C++.
Запустите VS.NET и создайте новый проект консольного приложения со стандартным именем ConsoleApplication1. Это имя определит имена файлов и пространства имен, которые создаст мастер приложений по умолчанию, а также имя исполнимого файла приложения. Поэтому его нужно выбирать обдуманно.
Мастер создаст заготовку простейшего класса C#, который имеет вид
using System;
namespace ConsoleApplication1
{
///
/// Summary description for Class1.
///
class Class1
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
}
}
}
Зайдите в каталог проекта и посмотрите, что мастер создал много подкаталогов различного назначения и много файлов. Это существенно отличается от конфигурации проекта, создаваемого мастером C++. Конфигурацию проекта, созданного мастером C#, можно посмотреть в панели Solution Explorer, установив режим этой панели щелчком по кнопке Show All Files 
Через панель Solution Explorer измените имя файла Class1.cs на HelloClass.cs. Вы увидете, что автоматически поменялось имя вкладки текстового редактора исходного кода оболочки, а на диске поменялось имя файла на HelloClass.cs 
В текстовом редакторе оболочки измените имя класса Class1 на HelloClass. Убедитесь, что в панели Class View имя класса также изменилось и изменилось имя в раскрывающемся списке в верхней части рабочей области оболочки
В текстовом редакторе измените имя пространства имен проекта с C onsoleApplication1 на ConsoleApp. Отметьте изменения в раскрывающемся списке в верхней части рабочей области оболочки и в панели Class View 
Давайте посмотрим на код класса, сгенерированного мастером. Там содержится единственный метод Main(). Это имя служебное и его менять нельзя!. Оно определяет точку входа в программу, с которой начнется выполнение приложения. Метод является общедоступным или закрытым (безразлично, все равно это точка входа), статическим, ничего не возвращающим. Другие же методы класса, предназначенные для вызова из клиента, обязательно должны быть общедоступными.
Любая строка определения данных или метода должна начинаться с оператора доступа. Если оператор доступа отсутствует, то соответствующая программная единица считается внутренней по умолчанию. Это же правило полностью относится и к структурам (в отличие от языка C++ ).
Аргументами метода Main() являются
static void Main(string[] args) {...} |
public static void Main(string[] args) {...} |
public static void Main() {...} |
private static void Main(string[] args) {...} |
private static void Main() {...} |
static int Main(string[] args) {... return 0; } |
public static int Main(string[] args) {... return 0; } |
public static int Main() {... return 0; } |
private static int Main(string[] args) {... return 0; } |
private static int Main() {... return 0; } |
Заполните метод Main() следующим кодомusing System;
namespace ConsoleApp
{
///
/// Summary description for Class1.
///
class HelloClass
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
Console.WriteLine("Привет студентам от Снеткова!!!");
while(true);
}
}
}
Постройте и выполните приложение. Должен получиться следующий результат
Сейчас мы запустим приложение через командную строку, чтобы дальше исследовать механизм передачи
Нажмите кнопку Пуск панели задач TaskBar Windows.
Выполните команду системного меню Выполнить...
Введите в поле окна Запуск программы команду cmd
Используя команды cd \, cd .., dir, спуститесь вниз по дереву каталогов проекта ConsoleApplication1\bin\Debug в каталог Debug, в котором находится исполнимый файл ConsoleApplication1.exe.
Выполните команду ConsoleApplication1. Должно запуститься наше приложение.
Нажмите комбинацию клавиш Ctrl-C или Ctrl-Break, чтобы прервать работу приложения, зацикленного на бесконечном выполнении предложения while(true) ;Оставьте открытым окно командной строки и приступим к модификации нашего проекта.
Вернемся к медоту Main() нашего приложения, вернее к его заголовку. Он выглядит так
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
Console.WriteLine("Привет студентам от Снеткова!!!");
while(true);
}
Здесь квадратные скобки есть ничто иное, как псевдоним объявления экземпляра класса массива System.Array. Заголовок мог бы выглядеть так
string System.Array args
Измените метод Main() так, чтобы внутри него перебирались using System;
namespace ConsoleApp
{
///
/// Summary description for Class1.
///
class HelloClass
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
// Выводим параметры на консоль
for(int i=0; i < args.Length; i++)
{
Console.WriteLine("Arg: {0}", args[i]);
}
Console.WriteLine("Привет студентам от Снеткова!!!");
while(true);
}
}
}
Постройте приложение, выполнив команду меню оболочки Project/Build Solution
Вернитесь в окно командной строки Windows, в котором текущим должен быть установлен каталог ConsoleApplication1\bin\Debug с исполнимым файлом и наберите в командной строке следующий текст>ConsoleApplication1 Раз Два Три Четыре Пять
После нажатия клавиши Enter на экране должен появиться следующий результат
Arg: Раз Arg: Два Arg: Три Arg: Четыре Arg: Пять Привет студентам от Снеткова!!!
Нажмите комбинацию клавиш Ctrl-C или Ctrl-Break, чтобы прервать работу приложения, зацикленного на бесконечном выполнении предложения while(true) ;
Замените в методе Main() цикл for на foreach, получив такой кодusing System;
namespace ConsoleApp
{
///
/// Summary description for Class1.
///
class HelloClass
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
// Выводим параметры на консоль
/*
for(int i=0; i < args.Length; i++)
{
Console.WriteLine("Arg: {0}", args[i]);
}
*/
foreach(string s in args)
{
Console.WriteLine("Arg: {0}", s);
}
Console.WriteLine("Привет студентам от Снеткова!!!");
while(true);
}
}
}
Постройте приложение, выполнив команду меню оболочки Project/Build Solution
Вернитесь в окно командной строки Windows, в котором текущим должен быть установлен каталог ConsoleApplication1\bin\Debug с исполнимым файлом и наберите (либо нажмите клавишу со стрелкой вверх) в командной строке следующий текст>ConsoleApplication1 Раз Два Три Четыре Пять
После нажатия клавиши Enter на экране должен появиться тот же самый результат
Arg: Раз Arg: Два Arg: Три Arg: Четыре Arg: Пять Привет студентам от Снеткова!!!
Нажмите комбинацию клавиш Ctrl-C или Ctrl-Break, чтобы прервать работу приложения, зацикленного на бесконечном выполнении предложения while(true);Все объекты в C# создаются на куче и с помощью оператора new. Инициализация объектов при их создании выполняется функциями-членами класса, которые называются конструкторами. Конструкторы не нужно вызывать специально. Они вызываются автоматически при создании объекта и только один раз. Повторно вызвать конструктор как метод для уже существующего объекта невозможно.
Различают конструкторы по умолчанию и общие конструкторы. Конструктор по умолчанию не имеет аргументов. Программист может его определить сам или ситема предоставит его автоматически, если программистом не был объявлен ни один конструктор. Конструктор по умолчанию может быть у класса только один. Общие конструкторы имеют аргументы. Класс может иметь несколько общих конструкторов, только они должны различаться сигнатурой (количеством, типом и порядком следования аргументов), чтобы компилятор мог однозначно определить перегружаемый конструктор по сигнатуре в фактическом вызове.
Язык C# имеет существенное отличие от C++ в отношении конструктора по умолчанию. В C++ если программист не предусмотрел свой конструктор по умолчанию, то предоставляемый системой конструктор ничего с данными-членами класса не делает и они остаются неинициализированными. В C# конструктор по умолчанию, предоставляемый системой, производит инициализацию данных, большей частью нулем. Но если программист определил хоть один конструктор с параметрами, то конструктор по умолчанию системой уже не предоставляется и нужно определить свой конструктор по умолчанию, хоть с пустым телом.
Имена конструкторов должны совпадать с именем класса. Конструкторы должны иметь public.
Для иллюстрации сказанного продолжим модификацию нашего проекта консольного приложения ConsoleApplication1 в части объявления класса HelloClass.
Модифицируйте класс следующим образомusing System;
namespace ConsoleApp
{
class HelloClass
{
// Внутренние переменные
private int x, y;
// Конструкторы
public HelloClass() // Конструктор по умолчанию
{
Console.WriteLine("Конструктор по умолчанию");
}
public HelloClass(int a, int b) // Конструктор с параметрами
{
Console.WriteLine("Конструктор с параметрами");
x = a;
y = b;
}
// Сервисы класса
public int GetX(){return x;}
public int GetY(){return y;}
// Точка входа в программу
static void Main(string[] args)
{
// Создаем два объекта-экземпляра класса
HelloClass ob1 = new HelloClass(); // Вызов конструктора по умолчанию
Console.WriteLine("ob1: x = {0};
y = {1} \n", ob1.GetX(), ob1.GetY());
HelloClass ob2 = new HelloClass(10, 15); // Вызов общего конструктора
Console.WriteLine("ob2: x = {0};
y = {1}", ob2.GetX(), ob2.GetY());
while(true);
}
}
}
Постройте и запустите проект через кнопку Start оболочки, получим результатРезультат применения конструкторов
Конструктор по умолчанию ob1: x = 0; y = 0 Конструктор с параметрами ob2: x = 10; y = 15
Мы видим, что переменные x и y в первом объекте не остались брошенными, как это бы было в C++, а инициализировались нулями в соответствии с их типом.
Деструкторы в C# применять не нужно, поэтому они не предусмотрены синтаксисом и в C# даже нет зарезервированного слова delete. Программист не должен заботиться об освобождении затребованной у системы памяти на куче. Об этом позаботится специальный механизм, который называется сборщиком мусора ( GC - Garbare ). Сборщик мусора платформы . NET освободит память автоматически.
В последнем примере мы внутри класса HelloClass в методе Main() создавали объекты того же самого класса. Это вносит лишнюю путаницу в изучение кода. Функция Main() никакого отношения к классу HelloClass не имеет и присутствует там только из-за того, что глобальные функции в C# недопустимы и ее нужно куда-то приткнуть. Так давайте же вынесем ее в отдельный класс, чтобы отделить "котлеты от мух".
Измените код файла HelloClass.cs так, как показано нижеusing System;
namespace ConsoleApp
{
class HelloClass
{
// Внутренние переменные
private int x, y;
// Конструкторы
public HelloClass() // Конструктор по умолчанию
{
Console.WriteLine("Конструктор по умолчанию");
}
public HelloClass(int a, int b) // Конструктор с параметрами
{
Console.WriteLine("Конструктор с параметрами");
x = a;
y = b;
}
// Сервисы класса
public int GetX(){return x;}
public int GetY(){return y;}
}
//**************************************************************
class HelloApp
{
// Точка входа в программу
static void Main(string[] args)
{
// Создаем два объекта-экземпляра класса
HelloClass ob1 = new HelloClass(); // Вызов конструктора по умолчанию
Console.WriteLine("ob1: x = {0};
y = {1} \n", ob1.GetX(), ob1.GetY());
HelloClass ob2 = new HelloClass(10, 15); // Вызов общего конструктора
Console.WriteLine("ob2: x = {0};
y = {1}", ob2.GetX(), ob2.GetY());
while(true);
}
}
}
Здесь мы ничего кардинального на сделали, просто объявили новый класс и в его тело вынесли функцию Main().
Запустите приложение и получите тот же самый результатТот же результат после вынесения точки входа в самостоятельный класс
Конструктор по умолчанию ob1: x = 0; y = 0 Конструктор с параметрами ob2: x = 10; y = 15
Поменяйте местами порядок следования классов и опять постройте приложение. Ничего не изменилось. Но в C++ пришлось бы в этом случае применять упреждающее объявление класса HelloClass перед классом HelloApp, поскольку его однопроходный компилятор встретив использование класса HelloClass еще не дошел бы до объявления самого класса HelloClass.Мы в данном примере нехорошо поступили (ну, почти нехорошо), потому что принято каждый класс размещать в отдельном файле с названием, обычно совпадающем с именем класса. Исправим это.
Выполните команду Add Class в меню Project оболочки
Заполните окно мастера так
После нажатия кнопки Open мастер создаст новый файл с именем HelloApp.cs. Перенесите класс HelloApp из файла HelloClass в новый файл, при этом не забудьте изменить пространство имен на прежнее ConsoleApp. Не забудьте удалить код перенесенного класса HelloApp в прежнем файле HelloClass.cs. Окончательно новый файл должен выглядеть такusing System;
namespace ConsoleApp
{
class HelloApp
{
// Точка входа в программу
static void Main(string[] args)
{
// Создаем два объекта-экземпляра класса
HelloClass ob1 = new HelloClass(); // Вызов конструктора по умолчанию
Console.WriteLine("ob1: x = {0};
y = {1} \n", ob1.GetX(), ob1.GetY());
HelloClass ob2 = new HelloClass(10, 15); // Вызов общего конструктора
Console.WriteLine("ob2: x = {0};
y = {1}", ob2.GetX(), ob2.GetY());
while(true);
}
}
}
Постройте приложение и убедитесь, что выходные результаты остались прежнимиТот же результат после переноса класса с функцией Main() в отдельный файл
Конструктор по умолчанию ob1: x = 0; y = 0 Конструктор с параметрами ob2: x = 10; y = 15
В C++ в классе можно простые переменные только объявлять, а инициализировать их нужно в конструкторе или устанавливать путем присваивания функциями Set() из клиента. В C# такое ограничение отсутствует и данные-члены класса можно инициализировать при их объявлении в классе. Примеры
class Text
{
private int MyInt = 90;
private string MyString = "My initial value";
private HotRod viper = new HotRod(200, "Chucky", Color.Red);
.............................................................
}
Измените код класса HelloClass нашего примера так, как показано нижеusing System;
namespace ConsoleApp
{
class HelloClass
{
// Внутренние переменные
private int x = 100, y = 150;
// Конструкторы
public HelloClass() // Конструктор по умолчанию
{
Console.WriteLine("Конструктор по умолчанию");
}
public HelloClass(int a, int b) // Конструктор с параметрами
{
Console.WriteLine("Конструктор с параметрами");
x = a;
y = b;
}
// Сервисы класса
public int GetX(){return x;}
public int GetY(){return y;}
}
}
После построения проекта получим результатРезультат после прямой инициализации данных
Конструктор по умолчанию ob1: x = 100; y = 150 Конструктор с параметрами ob2: x = 10; y = 15
Важное замечание
Отметим, что подобная прямая инициализация членов-данных в структурах недопустима. Перегрузка конструктора по умолчанию в структурах тоже недопустима.
В C# различают структурные типы ( reference-based ) и ссылочные ( value-based ). Переменная структурного типа представляет закрепленное за ней само значение оперативной памяти. Переменная ссылочного типа содержит лишь адрес области оперативной памяти с размещаемым в этой области значением. Все структурные переменные размещаются на стеке, а ссылочные - на управляемой куче ( managed ). К структурным типам относятся перечисления и структуры, а к ссылочным - классы, массивы, указатели, интерфейсы.
Когда выполняется присваивание одной структурной переменной другой, то создается побитовая копия данных, которые размещены в разных областях стековой памяти и существуют независимо друг от друга. При выполнении присваивания ссылочных переменных создается копия адреса, после чего обе переменные будут ссылаться на одну и ту же область памяти на управляемой куче. То есть создается как-бы псевдонимы одной и той же области памяти. Это означает, что изменения объекта по одной ссылочной переменной равноценно изменениям по псевдониму
Создайте консольное приложение C# или модифицируйте уже созданное. Заполните файл с точкой входа следующим кодомusing System;
namespace Test
{
struct FOO
{
public int x, y;
}
class ValRefClass
{
static void Main(string[] args)
{
// Создаем объект на стеке
// При создании структуры с конструктором по умолчанию
// ключевое слово new является необязательным,
// а можно просто FOO f1;
FOO f1 = new FOO();
f1.x = 100;
f1.y = 100;
// Создается объект на стеке без ключевого слова new
// и осуществляется побитовое копирование в другую память
FOO f2 = f1;
// Вывод до изменения
Console.WriteLine("Содержимое объектов
f1 и f2 до изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
// Внесение изменений
f1.x = 1; f1.y = 2; f2.x = 3; f2.y = 4;
// Вывод после изменения
Console.WriteLine("\nСодержимое объектов
f1 и f2 после изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
while(true);
}
}
}
Результаты экрана
Содержимое объектов f1 и f2 до изменения Объект f1: x=100; y=100 Объект f2: x=100; y=100 Содержимое объектов f1 и f2 после изменения Объект f1: x=1; y=2 Объект f2: x=3; y=4
Измените в исходном коде для типа FOO ключевое слово struct на class, тем самым сразу получим ссылочный тип. Запустите программу - получится результатusing System;
namespace Test
{
class FOO
{
public int x, y;
}
class ValRefClass
{
static void Main(string[] args)
{
// Создаем объект на куче
// Теперь слово new является обязательным, а также
// обазательны круглые скобки конструктора по умолчанию
FOO f1 = new FOO();
f1.x = 100;
f1.y = 100;
// Создается не объект, а ссылку, и после копирования
// адреса получаем псевдоним на ту же область памяти
FOO f2 = f1;
// Вывод до изменения
Console.WriteLine("Содержимое объектов f1 и
f2 до изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
// Внесение изменений
f1.x = 1; f1.y = 2; f2.x = 3; f2.y = 4;
// Вывод после изменения
Console.WriteLine("\nСодержимое объектов f1 и
f2 после изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
while(true);
}
}
}
Результаты экрана
Содержимое объектов f1 и f2 до изменения Объект f1: x=100; y=100 Объект f2: x=100; y=100 Содержимое объектов f1 и f2 после изменения Объект f1: x=3; y=4 Объект f2: x=3; y=4
Здесь мы видим, что в обоих случаях мы изменяли один и тот же объект, который и сохранил последние значения. Теперь f1 и f2 - не более чем ссылки на один и тот же диапазон памяти в области управляемой кучи.
Для того, чтобы объекты, адресуемые переменными f1 и f2, занимали разные области памяти, нужно запросить память у операционной системы для каждого из них и сохранить возвращенные адреса в этих переменных.
Следующий код и полученный результат иллюстрируют сказанное
using System;
namespace Test
{
class FOO
{
public int x, y;
}
class ValRefClass
{
static void Main(string[] args)
{
// Создаем первый объект на куче
FOO f1 = new FOO();
f1.x = 100;
f1.y = 100;
// Создается второй объект на куче
FOO f2 = new FOO();
f2.x = 100;
f2.y = 100;
// Вывод до изменения
Console.WriteLine("Содержимое объектов f1 и
f2 до изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
// Внесение изменений
f1.x = 1; f1.y = 2; f2.x = 3; f2.y = 4;
// Вывод после изменения
Console.WriteLine("\nСодержимое объектов
f1 и f2 после изменения");
Console.WriteLine("Объект f1: x={0};
y={1}", f1.x, f1.y);
Console.WriteLine("Объект f2: x={0};
y={1}", f2.x, f2.y);
while(true);
}
}
}
Результаты экрана
Содержимое объектов f1 и f2 до изменения Объект f1: x=100; y=100 Объект f2: x=100; y=100 Содержимое объектов f1 и f2 после изменения Объект f1: x=1; y=2 Объект f2: x=3; y=4
Поставим некоторые предварительные вопросы и ответы на них в таблице
| Вопрос | Структурные типы | Ссылочные типы |
|---|---|---|
| Место размещения | Стек | Управляемая куча |
| Как будет представлена переменная, которой в качестве значения присвоен этот тип | В виде локальной побитовой копии типа | В виде указателя на область оперативной памяти, относящейся к объекту этого типа |
| Что может являться для них базовым типом | Эти типы могут производиться только напрямую от типа System.ValueType |
Могут производиться от любого другого типа, кроме System.ValueType, если этот тип не является закрытым |
| Может ли этот тип выступать производителем другого типа | Нет. Структурные типы всегда закрыты и дополнение их другими свойствами не предусмотрено. Поэтому они не могут выступать при наследовании в качестве базового для других типов | Да, если этот ссылочный тип не определен внутренне как закрытый |
| Как производится передача параметров этого типа в функцию | Только по значению, т.е. вызывающей функции передаются локальные копии значений переменных | Передаются как ссылки, т.е. вызывающей функции передаются адреса размещения соответствующих объектов в оперативной памяти |
Существует ли возможность переопределить встроенный метод Object.Finalize() |
Нет. Структурные типы никогда не размещаются в куче, поэтому к ним нельза применить функцию освобождения памяти | Да, но не напрямую (подробнее дальше...) |
| Можно ли определить конструкторы для этого типа | Да, но конструктор по умолчанию зарезервирован, поэтому все определяемые конструкторы должны иметь параметры | Да, сколько угодно, лишь бы они различались сигнатурой |
| Когда происходит разрушение переменной данного типа | Когда происходит выход из области видимости | Во время процесса сборки мусора в управляемой куче с помощью механизма Garbare (сборщик мусора) |
| Можно ли напрямую инициализировать данные при объявлении типа | Нет | Да |
| Можно ли перегружать конструктор по умолчанию | Нет, он зарезервирован за системой | Да, и обязательно, если определен хоть один общий конструктор |
Несмотря на все различия, структурные и ссылочные типы могут иметь и поддерживать как встроенные (стандартные), так и определенные пользователем (программистом) методы, свойства, события в необходимом количестве. Но удобно за структурными типами закреплять только поддержку сложных данных.
Продемонстрируем сказанное простым примером.
Внесите следующие изменения в файл Test.cs консольного приложения C# с именем Testusing System;
namespace Test
{
//**********************************************************
struct PERSON // Структурный тип
{
private string Name;// Прямая инициализация не допускается
private int Age;// Прямая инициализация не допускается
//****************************************
// Объявления перегружаемых конструкторов
// Конструктор по умолчанию перегружать нельзя!!!
//****************************************
public PERSON(string name, int age)
{
Name = name; Age = age;
}
//****************************************
public PERSON(int age, string name)
{
Age = age; Name = name;
}
//****************************************
// Сервисы
//****************************************
public string GetName()
{
return Name;
}
//****************************************
public void SetName(string name)
{
Name = name;
}
//****************************************
public int GetAge()
{
return Age;
}
//****************************************
public void SetAge(int age)
{
Age = age;
}
}
//**********************************************************
class Person // Ссылочный тип
{
private string Name = "Иванов";// Прямая инициализация допустима
private int Age = 20;// Прямая инициализация допустима
//****************************************
// Перегружаемые конструкторы
// Конструктор по умолчанию перегружать можно
//****************************************
public Person(){}
//****************************************
public Person(string name, int age)
{
Name = name; Age = age;
}
//****************************************
public Person(int age, string name)
{
Age = age; Name = name;
}
//****************************************
public Person(string name)
{
Name = name;
}
//****************************************
public Person(int age)
{
Age = age;
}
//****************************************
// Сервисы
//****************************************
public string GetName()
{
return Name;
}
//****************************************
public void SetName(string name)
{
Name = name;
}
//****************************************
public int GetAge()
{
return Age;
}
//****************************************
public void SetAge(int age)
{
Age = age;
}
}
class ValRefClass
{
static void Main()
{
// Создаем объект на стеке
PERSON petrov = new PERSON("Денис
Петров", 23);// Общий конструктор
PERSON empty = new PERSON();// Другой объект с конструктором по умолчанию
// Круглые скобки обязательны
// Создаем объект на куче
Person sergey = new Person(21, "
Сережа Жук!");
Person ivanov = new Person(); // Создание с конструктором по умолчанию
// Вывод из стека
Console.WriteLine("Содержимое объектов на стеке");
Console.WriteLine("Объект petrov: Имя={0};
возраст={1}",petrov.GetName(), petrov.GetAge());
Console.WriteLine("Объект empty: Имя={0};
возраст={1}",empty.GetName(), empty.GetAge());
Console.WriteLine("\n");
// Вывод из кучи
Console.WriteLine("Содержимое объектов на куче");
Console.WriteLine("Объект sergey: Имя={0};
возраст={1}",sergey.GetName(), sergey.GetAge());
Console.WriteLine("Объект ivanov: Имя={0};
возраст={1}",ivanov.GetName(), ivanov.GetAge());
while(true);
}
}
}
Результаты экрана
Содержимое объектов на стеке Объект petrov: Имя=Денис Петров; возраст=23 Объект empty: Имя=; возраст=0 Содержимое объектов на куче Объект sergey: Имя=Сережа Жук!; возраст=21 Объект ivanov: Имя=Иванов; возраст=20
Все типы в C#, как структурные, так и ссылочные, являются производными от одного общего предка - класса System.Object. Это подразумевается по умолчанию и нет необходимости явно указывать этот базовый класс при объявлении
class Hello // Предок System.Object подразумевается по умолчанию
{
....................
}
struct HELLO // Предок System.Object подразумевается по умолчанию
{
....................
}
class Hello : System.Object
{
....................
}
struct HELLO : System.Object
{
....................
}
using System;
namespace Test
{
class Hello : Object // Укороченно, т.к. пространство имен определено
{
....................
}
struct HELLO : object // С маленькой буквы, потому что предусмотрен синоним
{
....................
}
}
Ниже приведен код объявления библиотечного класса System.Object
namespace System
{
public class Object
{
public Object();
public virtual Boolean Equals(Object obj);
public virtual Int32 GetHashCode();
public Type GetType();
public virtual String ToString();
protected virtual void Finalize();
protected Object MemberwiseClone();
}
}
Предназначение каждого метода приведено в таблице
| Метод | Описание |
|---|---|
Equals() |
Возвращает true, если сравниваемые сущности занимают одну и ту же область памяти. По умолчанию настроен для сравнивания объектов ссылочных типов. Для применения к объектам структурных типов этот метод и сопутствующий ему метод GetHashCode() нужно перегрузить своими методами. |
GetHashCode() |
Возвращает целочисленное значение ID, идентифицирующее конкретный экземпляр объекта данного типа. Генерирует уникальный хэш. |
GetType() |
Метод возвращает объект Type(), полностью описывающий тот объект, из которого этот метод был вызван. Это метод идентификации времени выполнения ( Runtime Type Identification - RTTI ) , который предусмотрен во всех объектах. |
ToString() |
Возвращает fully - полностью определенное имя. Если тип определен вне какого-либо пространства имен, возвращается только имя класса без именного пути. Этот метод может быть перегружен для предоставления информации о внутреннем состоянии объекта в формате имя-значение. |
Finalize() |
Основное назначение этого метода - освободить все ресурсы, занятые объектом данного класса, перед его удалением для корректной работы сборщика мусора gc - Garbare . |
MemberwiseClone() |
Этот метод предназначен для создания еще одной ссылки на область, занимаемую объектом данного типа в оперативной памяти. Этот метод не может быть перегружен. Если требуется реализовать поддержку создания полной копии объекта в оперативной памяти, то необходимо реализовать поддержку интерфейса |
В следующем примере продемонстрированы возможности некотороых из перечисленных методов
using System;
namespace Test
{
//*******************************************************
class ObjTest
{
}
//*******************************************************
class App
{
static void Main()
{
// Создаем объект на куче
ObjTest obj1 = new ObjTest();
// Выводим информацию, возвращенную методами
// базового класса System.Object
Console.WriteLine("ToString(): {0}",
obj1.ToString());
Console.WriteLine("GetHashCode(): {0}",
obj1.GetHashCode());
Console.WriteLine("GetType(): {0}",
obj1.GetType().ToString());
// Создаем еще две ссылки на объект obj1
ObjTest obj2 = obj1;
object obj3 = obj2;
// Проверяем, действительно ли все три ссылки
// указывают на одну и ту же область оперативной памяти
if(obj1.Equals(obj2) obj2.Equals(obj3))
Console.WriteLine("Адресация памяти совпадает");
while(true);
}
}
}
Результаты экрана
ToString(): Test.ObjTest GetHashCode(): 2 GetType(): Test.ObjTest Адресация памяти совпадает
Перегружать в производном классе можно только методы, объявленные в базовом классе как виртуальные с префиксом virtual. Перегружаемый метод должен маркироваться ключевым словом override.
Методы, которые наследуются типами от базового класса System.Object, иногда могут не устраивать пользователя (программиста) при разработке своих типов. Поэтому может возникнуть необходимость перегружать некоторые из них. Покажем это на примерах.
Разработаем класс Person, в котором перегрузим метод ToString() так, чтобы он выдавал не имя класса, а внутреннее состояние объекта, из которого он вызывается.
Перегрузим, также, метод Equals() базового класса. Вспомним, что он возвращает значение true в случае, если сравниваемые ссылки указывают на один и тот же объект оперативной памяти. Пусть этот метод возвращает true, если у сравниваемых объектов одинакового типа совпадает внутреннее состояние, т.е. значения всех внутренних переменных совпадают.
Если мы перегружаем метод Equals(), то обязательно нужно перегрузить и сопутствующий ему метод GetHashCode(). Если етого не сделать, то компилятор выдаст предупреждение. Метод GetHashCode() возвражает числовое значение уникального идентификатора ID объекта в памяти. Чаще всего это значение используется в коллекциях, работающих с хэшами объектов. Нам достаточно применить эту функцию к любой переменной типа, производного от базового класса System.Object. Но любые встроенные в C# типы происходят от этого базового класса.
using System;
namespace Test
{
//**********************************************************************
// Класс, хранящий информацию о человеке
//**********************************************************************
class Person
{
//******************************************
// Закрытые (private) данные о человеке
//******************************************
string Name;// ФИО
string Inn; // Номер налогоплательщика
int Age; // Возраст
//******************************************
// Конструкторы
//******************************************
public Person(){} // Пустой конструктор по умолчанию
//******************************************
public Person(string name, string inn, int age)
{
Name = name;
Inn = inn;
Age = age;
}
//******************************************
// Сервисы
//******************************************
public void Set(string name, string inn, int age)
{
Name = name;
Inn = inn;
Age = age;
}
//******************************************
public string GetName()
{
return Name;
}
//******************************************
public string GetInn()
{
return Inn;
}
//******************************************
public int GetAge()
{
return Age;
}
//******************************************
// Перегрузка библиотечного метода ToString(),
// унаследованного от класса System.Object
//******************************************
public override string ToString()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("Ф.И.О. = " + this.Name);
sb.Append("\nИНН = " + this.Inn);
sb.Append("\nВозраст = " + this.Age);
return sb.ToString ();// Генерируется автоматически
}
//******************************************
// Перегрузка библиотечного метода Equals(),
// унаследованного от класса System.Object
//******************************************
public override bool Equals(object obj)
{
Person tmp = (Person) obj; // Создаем еще одну ссылку
// Проверяем совпадения значений переменных
// текущего объекта this и переданного объекта obj
if(tmp.Name == this.Name
tmp.Inn == this.Inn
tmp.Age == this.Age)
return true;
else
return false;
}
//******************************************
// Перегрузка библиотечного метода GetHashCode(),
// связанного с методом Equals()
//******************************************
public override int GetHashCode()
{
// Применяем к переменной Inn производного типа string
return Inn.GetHashCode();
}
}
//**********************************************************************
// Клиент Main()
// Точка входа в приложение
//**********************************************************************
class App
{
static void Main()
{
// Создаем два объекта с одинаковыми состояниями
Person person1 = new Person("Серега Жук",
"2222-33-5555", 21);
Person person2 = new Person("Серега Жук",
"2222-33-5555", 21);
Console.WriteLine("Прервая проверка для
одинаковых объектов");
// Используем замещенный для сравнения внутренного сотояния метод Equals()
if(person1.Equals(person2) person1.GetHashCode() ==
person2.GetHashCode())
Console.WriteLine("Состояние объектов person1 и
person2 совпадает");
else
Console.WriteLine("Состояние объектов person1 и
person2 несовпадает");
// Меняем состояние одного из объектов
person2.Set("Андрей Косенок",
"1111-22-4444", 22);
Console.WriteLine("\nПовторная проверка для
различающихся объектов");
// Используем замещенный для сравнения внутренного
сотояния метод Equals()
if(person1.Equals(person2) person1.GetHashCode() ==
person2.GetHashCode())
Console.WriteLine("Состояние объектов person1 и
person2 совпадает");
else
Console.WriteLine("Состояние объектов person1 и
person2 несовпадает");
// Теперь используем замещенный (перегруженный) метод ToString()
Console.WriteLine("\nСодержимое объекта person1");
Console.WriteLine(person1.ToString());
Console.WriteLine("\nСодержимое объекта person2");
// Метод WriteLine() сам автоматически вызывает метод ToString()
Console.WriteLine(person2);
while(true);
}
}
}
Результаты экрана
Прервая проверка для одинаковых объектов Состояние объектов person1 и person2 совпадает Повторная проверка для различающихся объектов Состояние объектов person1 и person2 несовпадает Содержимое объекта person1 Ф.И.О. = Серега Жук ИНН = 2222-33-5555 Возраст = 21 Содержимое объекта person2 Ф.И.О. = Андрей Косенок ИНН = 1111-22-4444 Возраст = 22
Мы испробовали работу метода Equals() в двух вариантах:
true, если ссылки имеют одинаковое значение, что означает, что обе ссылки указывают на один и тот же объектНо разработчики класса System.Object предусмотрели для обоих этих целей специално два статических метода
System.Object.Equals() - сравнивает значения составляющих-членов двух объектовSystem.Object.ReferenceEquals() - сравнивает значения ссылок на два объектаИспытаем эти методы, изменив соответствующим образом код предыдущего примера. При этом следует помнить, что эти два метода статические. Это значит, что они существуют применительно к классу, а не к конкретному объекту. И вызывать их нужно с использованием имени класса. И еще напомним, что для класса Object простанства имен System определен псевдоним object как стандартный (встроенный) тип языка C#.
И так, листинг примера...
using System;
namespace Test
{
//**********************************************************************
// Класс, хранящий информацию о человеке
//**********************************************************************
class Person
{
//******************************************
// Закрытые (private) данные о человеке
//******************************************
string Name;// ФИО
string Inn; // Номер налогоплательщика
int Age; // Возраст
//******************************************
// Конструкторы
//******************************************
public Person(){} // Пустой конструктор по умолчанию
//******************************************
public Person(string name, string inn, int age)
{
Name = name;
Inn = inn;
Age = age;
}
}
//**********************************************************************
// Клиент Main()
// Точка входа в приложение
//**********************************************************************
class App
{
static void Main()
{
// Создаем два объекта с одинаковыми состояниями
Person person1 = new Person("Sally",
"2222-33-5555", 21);
Person person2 = new Person("Sally",
"2222-33-5555", 21);
string p1 = "Привет всем!";
string p2 = "Привет всем!";
// Сравниваем внутреннее состояние двух объектов
Console.WriteLine("Содержимое двух объектов
одинаково? - {0}", object.Equals(p1, p2));
// Сравниваем значения ссылок
Console.WriteLine("Занимают они одну память? - {0}",
object.ReferenceEquals(person1, person2));
while(true);
}
}
}
Результаты экрана
Содержимое двух объектов одинаково? - True Занимают они одну память? - False
Любой встроенный в C# тип данных - это лишь псевдоним для существующего типа, определенного в пространстве имен System. В таблице приведены все системные типы данных и соответствующие им псевдонимы C#, а также информация о совместимости с общей средой выполнения CLR ( (Common Language Specification).
| Псевдоним C# | Соответствие CLS | Системный тип | Диапазон | Пояснения |
|---|---|---|---|---|
void |
+ | Void |
Пусто | Применяется для соблюдения синтаксиса |
sbyte |
- | SByte |
-128 —127 | Знаковое 8 бит |
byte |
+ | Byte |
0 —255 | Беззнаковое 8 бит |
short |
+ | Int16 |
-32'768 —32'767 | Знаковое 16 бит |
ushort |
- | UInt16 |
0 —65'535 | Беззнаковое 16 бит |
int |
+ | Int32 |
-2'147'483'648 —2'147'483'647 | Знаковое 32 бит |
uint |
- | UInt32 |
0 —4'294'967'295 | Беззнаковое 32 бит |
long |
+ | Int64 |
-9'223'372'036'854'775'808 —9'223'372'036'854'775'807 | Знаковое 64 бит |
ulong |
- | UInt64 |
0 —18'446'774'073'709'551'615 | Беззнаковое 64 бит |
char |
+ | Char |
U+0000 —U+FFFF | Для описания только одного символа Unicode 16 бит |
float |
+ | Single |
1.5x10-45 —3.4x1038 | Знаковое с плавающей точкой 32 бит |
double |
+ | Double |
5.0x10-324— 1.7x10308 | Знаковое с плавающей точкой 64 бит |
bool |
+ | Boolean |
true или false |
Логическое |
decimal |
+ | Decimal |
100 —1028 | Знаковое 96 бит |
string |
+ | String |
Ограничено только системной памятью | Для описания строки символов Unicode любой длины |
object |
+ | Object |
Практически все что угодно. Все типы происходят от System.Object, поэтому объектом является все |
Класс, базовый для всех типов в . NET |
Из приведенных в таблице типов только часть является CLR -совместимой. Если мы создаем приложение, которое должно работать в многоязыковой среде, то желательно ограничиться только -NET.
Системные типы как классы можно посмотреть в суфлере кода IntelliSense, написав в текстовом редакторе слово System и точку

То же самое можно получить, выполнив команду запуска IntelliSense как показано на картинке

То же самое можно получить, написав в текстовом редакторе кода внутри любого метода ключевое слово new

Во всех случаях при этом нужно, чтобы курсор конструктивно находился внутри какого-нибудь метода класса или структуры.
Поскольку тип int - это всего лишь более простая и удобная запись системного типа Int32, то и с числовыми константами можно обращаться как с переменными соответствующего типа. Ниже приведен пример допустимого синтаксиса
using System;
namespace Test
{
class Test
{
}
class App
{
static void Main()
{
Console.WriteLine("ToString(): {0}", 1234567.ToString());
while(true);
}
}
}
Результат выполнения примера
ToString(): 1234567
Большинство встроенных типов являются структурными типами, потому что происходят не от базового типа System.Object напрямую, а от производного от System.Object типа System.ValueType. Этот класс-надстройка нужен как раз для того, чтобы переменные системных типов были структурными и представлялись значением, а не ссылкой. Приведем пример
using System;
namespace Test
{
class App
{
static void Main()
{
System.Int32 xInt = 100;
System.Int32 yInt = 100;
System.Object xObj = 1000;
System.Object yObj = 1000;
// Сравниваются значения
if(xInt == yInt)
Console.WriteLine("Значения одинаковые");
else
Console.WriteLine("Значения различаются");
// Сравниваются ссылки
if(xObj == yObj)
Console.WriteLine("Ссылки одинаковые");
else
Console.WriteLine("Ссылки различаются");
while(true);
}
}
}
Результат выполнения примера
Значения одинаковые Ссылки различаются
Системные типы как классы имеют свои методы и свойства

Отметим удобные свойства и , которые содержат пределы поддерживаемого данным типом числового диапазона. Составим для иллюстрации небольшой тест для типа System.Double (псевдоним в C# - double )
using System;
namespace Test
{
class App
{
static void Main()
{
// Работаем как со структурным типом
System.Double dPI = System.Math.PI;
double dE = Math.E;
// Используем свойства и перегруженные методы
// структурного типа System.ValueType
Console.WriteLine("\tОбъект PI:");
Console.WriteLine("MinValue: \t{0}", System.Double.MinValue);
Console.WriteLine("MaxValue: \t{0}", System.Double.MaxValue);
Console.WriteLine("String Value: \t{0}", dPI.ToString());
Console.WriteLine("Value: \t\t{0}", dPI);
Console.WriteLine("TypeName: \t{0}", dPI.GetType().ToString());
Console.WriteLine("\n\tОбъект E:");
Console.WriteLine("MinValue: \t{0}", double.MinValue);
Console.WriteLine("MaxValue: \t{0}", double.MaxValue);
Console.WriteLine("String Value: \t{0}", dE.ToString());
Console.WriteLine("Value: \t\t{0}", dE);
Console.WriteLine("TypeName: \t{0}", dE.GetType().ToString());
while(true);
}
}
}
Результат выполнения примера
Объект PI: MinValue: -1,79769313486232E+308 MaxValue: 1,79769313486232E+308 String Value: 3,14159265358979 Value: 3,14159265358979 TypeName: System.Double Объект E: MinValue: -1,79769313486232E+308 MaxValue: 1,79769313486232E+308 String Value: 2,71828182845905 Value: 2,71828182845905 TypeName: System.Double
Следует отметить, что логический тип System.Boolean (или псевдоним bool ) ожидают только логические значения true или false. Никакие числовые значения им присваивать нельзя. Для текстовых данных в C# используются только два типа: string и char. Никакие другие разновидности наподобие char*, wchar_t*, LPSTR, LPCSTR, BSTR, OLECHAR и другие в C# не допустимы. Тип char описывает только одиночный символ Unicode (char symbol = 'П';), тип string описывает строку (набор) символов Unicode любой длины.
Если создаются переменные встроенных типов внутри класса (а не внутри метода класса), то компилятор при создании экземпляра сразу инициализирует этот экземпляр подходящим значением по умолчанию. Запустите следующий код в режиме отладки Debug, предварительно поставив точку останова на последнем операторе функции Main().
using System;
namespace Test
{
class DefValObject
{// Переменные встроенных типов создаются внутри класса
sbyte theSByte;
byte theByte;
short theShort;
ushort theUShort;
int theInt;
uint theUInt;
long theLong;
ulong theULong;
char theChar;
float theFloat;
double theDouble;
bool theBool;
decimal theDecimal;
string theString;
object theObject;
}
class App
{
static void Main()
{
DefValObject val = new DefValObject();
while(true); // Здесь поставим точку останова для отладчика
}
}
}
После останова программы в точке останова выполните последовательность нажатий клавиш < Ctrl+Alt+V, L >, чтобы вызвать окно Locals оболочки. Мы видим, что данные встроенных типов после создания объекта инициализируются.

Но если переменные встроенных типов создаются внутри какого-нибудь метода класса, то выполняется точно такая же автоматическая инициализация этих переменных.
Проверим это на подобном примере, только определения переменных перенесем внутрь метода класса.
using System;
namespace Test
{
class DefValObject
{
public void MyMethod() // Переменные внутри метода
{
sbyte theSByte;
byte theByte;
short theShort;
ushort theUShort;
int theInt;
uint theUInt;
long theLong;
ulong theULong;
char theChar;
float theFloat;
double theDouble;
bool theBool;
decimal theDecimal;
string theString;
object theObject;
return;// Здесь поставим точку останова для отладчика
}
}
class App
{
static void Main()
{
DefValObject val = new DefValObject();
val.MyMethod();
}
}
}

Константы определяют в виде переменных с ключевым словом const. Это заставляет компилятор следить за тем, чтобы программист случайно не попытался переприсвоить им новые значения. Константы можно определять и на уровне методов, но гораздо чаще они используются на уровне определений классов.
using System;
namespace Test
{
class DefConst
{
// Определение констант на уровне класса
private const int myIntConst = 5;
private const string myStringConst =
"Это константа уровня класса";
public void MyMethod()
{
// Определение констант на уровне метода
const int myIntConst = 10;
const string myStringConst =
"Это локальная константа метода";
Console.WriteLine("Константа класса: {0}",
DefConst.myIntConst);
Console.WriteLine("Константа класса: {0}",
DefConst.myStringConst);
Console.WriteLine("\n");
Console.WriteLine("Константа метода: {0}", myIntConst);
Console.WriteLine("Константа метода: {0}", myStringConst);
}
}
class App
{
static void Main()
{
DefConst instance = new DefConst();
instance.MyMethod();
while(true);
}
}
}
Результат выполнения примера
Константа класса: 5 Константа класса: Это константа уровня класса Константа метода: 10 Константа метода: Это локальная константа метода
Иногда удобно использовать служебный класс, созданный с единственной целью - служить в качестве хранилища констант. При этом нелишним будет позаботиться о том, чтобы случайно не создать экземпляр этого класса. Чтобы запретить создавать экземпляры класса, можно использовать любой из двух способов
Способ 1. Достаточно объявить конструктор класса как private
using System;
namespace Test
{
class DefConst
{
// Определение констант на уровне класса
public const int myIntConst = 5;
public const string myStringConst = "Это константа уровня класса";
private DefConst(){}
}
class App
{
static void Main()
{
//Error DefConst instance = new DefConst();
Console.WriteLine("Константа класса: {0}",
DefConst.myIntConst);
Console.WriteLine("Константа класса: {0}",
DefConst.myStringConst);
//Error Console.WriteLine("Константа класса: {0}", myIntConst);
//Error Console.WriteLine("Константа класса: {0}", myStringConst);
while(true);
}
}
}
Результат выполнения примера
Константа класса: 5 Константа класса: Это константа уровня класса
Способ 2. Достаточно объявить весь класс как абстрактный. Экземпляры абстрактных классов создавать нельзя (компилятор проследит за этим)
using System;
namespace Test
{
abstract class DefConst
{
// Определение констант на уровне класса
public const int myIntConst = 5;
public const string myStringConst =
"Это константа уровня класса";
//Конструктор вообще не нужен
}
class App
{
static void Main()
{
//Error DefConst instance = new DefConst();
Console.WriteLine("Константа класса: {0}",
DefConst.myIntConst);
Console.WriteLine("Константа класса: {0}",
DefConst.myStringConst);
//Error Console.WriteLine("Константа класса: {0}", myIntConst);
//Error Console.WriteLine("Константа класса: {0}", myStringConst);
while(true);
}
}
}
Результат выполнения примера
Константа класса: 5 Константа класса: Это константа уровня класса
Использование отдельного класса для хранения констант удобно тем, что C# не позволяет объявлять глобальные константы.
Ключевое слово const нельзя использовать как часть объявления метода. Для этих целей служат модификаторы in и out.
Мы знаем, что ссылочный тип объекта позволяет компактно передавать информацию об объекте любой функции, но все изменения, вносимые функцией, немедленно влияют на сам объект. В ссылочном виде в стеке хранится только ссылка на объект, а сам объект находится в управляемой куче оперативной памяти. В структурном виде в стеке хранится сам объект и при передаче функции передается копия объекта. Функция изменяет только копию переданного объекта, что никак не отражается на самом объекте.
Иногда может возникнуть необходимость передать в функцию ссылку, но внутри функции выполнить преобразование объекта в структурную форму. Такой процесс называется распаковкой. Создание ссылки на структурный объект называется упаковкой. Но следует иметь ввиду, что структурный объект всегда локальный на стеке, он разрушается при выходе из области видимости. Поэтому может оказаться, что ссылка будет ссылаться на уже несуществующий объект.
Приведем пример, где структурный объект упаковывается перед передачей в функцию, затем в ней распаковывается и используется. При распаковке создается локальная самостоятельная копия структурного объекта.
using System;
namespace Test
{
//*********************************************************************
struct PERSON // Структурный тип
{
// Данные класса без оператора доступа считаются private
string Name;// Прямая инициализация в структурах не допускается
int Age;// а в классах допускается
//****************************************
// Конструктор
//****************************************
public PERSON(string name, int age)
{
Name = name; Age = age;
}
//****************************************
// Сервисы
//****************************************
public string GetName()
{
return Name;
}
//****************************************
public int GetAge()
{
return Age;
}
//****************************************
public void SetAge(int age)
{
Age = age;
}
}
//*********************************************************************
class App
{
static void Main()
{
// Создаем объект на стеке
PERSON petrov = new PERSON("Денис
Петров", 20);
// Вывод
Console.WriteLine("Объект petrov: Имя={0};
возраст={1}",petrov.GetName(), petrov.GetAge());
// Упаковываем в ссылку для передачи куда-то
object pack = petrov;
PutClass.Put(pack);
while(true);
}
}
//*********************************************************************
class PutClass
{
public static void Put(object pack)
{
// В месте получения распаковываем в самостоятельную копию
PERSON unPack = (PERSON)pack;
// Изменяем копию для проверки, что это действительно копия
unPack.SetAge(33);
Console.WriteLine("Объект unPack: Имя={0};
возраст={1}", unPack.GetName(), unPack.GetAge());
}
}
}
Результат выполнения примера
Объект petrov: Имя=Денис Петров; возраст=20 Объект unPack: Имя=Денис Петров; возраст=33
Для организации ввода-вывода в C# предусмотрен класс System.Console. Он же предназначен для вывода сообщений об ошибках. Естественно, что этот класс чаще применяется в консольных приложениях. Системную консоль иногда называют командной строкой.
Основными методами класса являются WriteLine() и Write(). В отличие от метода Write() метод WriteLine() добавляет в конец строки символ новой строки \n автоматически. Метод ReadLine() позволяет считывать информацию с \n, метод Read() считывает с
Создайте консольное приложение с именем Test
Измените имя исходного файла на App.cs
Измените пространство имен на Test, а имя класса на BasicIO
Уберите аргументы у функции Main()
Заполните метод Main() следующим кодомusing System;
namespace Test
{
class BasicIO
{
static void Main()
{
// Тестируем пользователя
string name;
Console.Write("Введите Ваше имя: ");
name = Console.ReadLine(); // Читаем ввод после нажатия Enter
string age;
Console.Write("Введите Ваш возраст (лет): ");
age = Console.ReadLine(); // Читаем ввод после нажатия Enter
// Выводим полученную информацию на консоль
Console.WriteLine("\nПривет, дорогой {0}!
Вам {1} лет.", name, age);
while(true);
}
}
}
Результаты экрана
Введите Ваше имя: Снетков В.М. Введите Ваш возраст (лет): 55 Привет, дорогой Снетков В.М.! Вам 55 лет.
Нажмите комбинацию клавиш Ctrl-C или Ctrl-Break, чтобы прервать работу приложения, зацикленного на бесконечном выполнении предложения while(true);В C++ в консольном выводе функции prinf() применяются спецификаторы форматирования %d, %s, %c и т.д. в отдельности для каждого C# в консольном выводе функций Console.Write() и Console.Writeln() применяются универсальные индексированные метки {0}, {1}, {2}, и т.д., содержащие порядковый номер выводимого параметра в списке параметров. Эти подстановочные выражения обозначают
Параметры форматирования можно применять и для массивов. При этом подстановочные выражения будут указывать на элементы этого массива.
В подстановочном выражении можно указывать и более точные настройки формата с помощью дополнительных параметров форматирования. Параметры форматирования приведены в таблице, причем их можно указывать как строчными так и прописными буквами.
| Параметр | Значение |
|---|---|
C, c |
Используется для вывода значений в денежном ( Currency ) формате. По умолчанию перед выводимым значением подставляется символ доллара ($), хотя можно отменить подстановку этого символа при помощи объекта NumberFormatInfo. |
D, d |
Используется для вывода целых десятичных значений ( Decimal ). После этого символа можно указать количество значащих цифр. |
E, e |
Для вывода значений в экспоненциальном формате ( Scientific ). |
F, f |
Вывод значений с фиксированной точкой ( Fixed-point ). |
G, g |
Общий ( General ) формат.Применяется для вывода значений с фиксированной точностью или в экспоненциальном формате. |
N, n |
Стандартное числовое форматирование ( Number ) с использованием разделителей (пробелов или запятых) между разрядами. |
X, x |
Вывод значений в шестнадцатеричном формате ( Hexadecimal ). Если использовать заглавную X, то символы в шестнадцатеричном формате также будут заглавными. |
R, r |
Округление ( Round-trip ) |
Символы форматирования следуют в индексированных знакоместах сразу за номером подставляемого параметра через двоеточие:
{0:C}, {1:d}, {2:X} и т.д.
Параметры форматирования можно использовать для формирования любых символьных строк с использованием статического метода String.Format().
Измените консольное приложение с именем Test, заполнив метод Main() следующим кодомusing System;
namespace Test
{
class BasicIO
{
static void Main()
{
// Форматирование без параметров
string theString = "Привет всем!";
int theInt = 15;
float theFloat = 9.99F;
double theDouble = 123.456789;
BasicIO theClass = new BasicIO();
// Вывод
Console.WriteLine("Без параметров форматирования:");
Console.WriteLine("string: {0}\nint: {1}\nfloat:
{2}\ndouble: {3}\nobject: {4}"
, theString, theInt, theFloat, theDouble, theClass);
Console.Write("\n\n");
// Форматирование массива
object[] array = {"Привет!", 20.9, 1,
"55", Math.PI};
Console.WriteLine("Элементы массива:\n{0}; {1};
{2}; {3}; {4}", array);
Console.Write("\n\n");
// Форматирование с параметрами
Console.WriteLine("С параметрами форматирования:");
Console.WriteLine("C format: {0:C}", 99989.987);
Console.WriteLine("D9 format: {0:D9}", 99999);
Console.WriteLine("E format: {0:E}", .31415926538 * 10);
Console.WriteLine("F format: {0:F3}", 55555.6666);
Console.WriteLine("N format: {0:N}", 99999);
Console.WriteLine("X format: {0:X}", 99999);
Console.WriteLine("x format: {0:x}", 99999);
Console.Write("\n\n");
// Предварительное форматирование в символьную строку
string str;
str = String.Format("C format: {0:C}", 99989.987);
Console.WriteLine("Предварительное
форматирование в символьную строку:");
Console.WriteLine(str);
// Аналогично, с предварительным форматированием...
str = "ФИО: {{ {0} }}, Возраст: {{ {1} }}";
str = String.Format(str, "Снетков В.М.", 55);
Console.WriteLine("\n\nПредварительное
форматирование в символьную строку:");
Console.WriteLine(str);
while(true);
}
}
}
Результат форматирования вывода
Без параметров форматирования:
string: Привет всем!
int: 15
float: 9,99
double: 123,456789
object: Test.BasicIO
Элементы массива:
Привет!; 20,9; 1; 55; 3,14159265358979
С параметрами форматирования:
C format: 99 989,99р.
D9 format: 000099999
E format: 3,141593E+000
F format: 55555,667
N format: 99 999,00
X format: 1869F
x format: 1869f
Предварительное форматирование в символьную строку:
C format: 99 989,99р.
Предварительное форматирование в символьную строку:
ФИО: { Снетков В.М. }, Возраст: { 55 }
Метод - это способ упаковки кода, который рассматривается как единое целое и может быть выполнен в процессе работы программы. В C# не существует глобальных методов - любой метод обязательно должен быть членом класса или структуры.
Методы могут принимать или не принимать параметры, могут возвращать или не возвращать значения, могут быть методами класса (статическими) или
Для каждого метода существует свой уровень доступа, который определяет, откуда можно будет обратиться к этому методу. Для указания уровня доступа существуют специальные модификаторы. Они приведены в таблице
| Модификатор | Назначение |
|---|---|
public |
Модификатор общедоступности метода |
private (по умолчанию) |
Метод будет доступен только внутри класса, где он определен. Если при объявлении метода модификатор явно не указан, то используется по умолчанию модификатор private |
protected |
Метод будет доступен только внутри самого класса и внутри любого производного от него класса. Для остальных вызовов из внешнего мира такой метод будет недоступен |
internal |
Метод будет доступен из всех классов внутри сборки, в которой он определен. Из-за пределов этой сборки обратиться к нему будет нельзя |
protected internal |
Доступен для самого класса и его наследников внутри сборки, где они определены |
Ниже приведены заготовки кода, иллюстрирующие уровни доступа к методам.
// Уровни доступа к методам
class SomeClass
{
// Доступен отовсюду
public void Method1(){...}
// Доступен только из методов класса SomeClass
private void Method2(){...}
// Доступен из класса SomeClass и его наследников
// на любом нижестоящем уровне иерархии наследования
protected void Method3(){...}
// Доступен из всех классов той же сборки
internal void Method4(){...}
// Доступен для себя и наследников из той же сборки
protected internal void Method5(){...}
// Только для внутреннего пользования
void Method6(){...}
}
Сейчас мы продемонстрируем то, что в C# имена классов, имена пространства имен, идентификаторы типов данных и имена методов можно писать русскими буквами. Только ключевые слова должны оставаться неизменными, а все идентификаторы, которые волен выбирать программист, можно излагать кириллицей. Мы просто это продемонстирируем и заметим, что это плохая практика, хотя такая возможность есть.
using System;
namespace МоеПространствоИмен
{
class Жалобщик
{
// Объект для генерации случайных чисел
// rand - закрытая private переменная класса
System.Random rand = new System.Random();
private int ДатьЧисло(short лимит)
{
// Генерирует целое число в диапазоне
// от нуля до "лимит"
return rand.Next(лимит);
}
public string ГенераторЖалоб()
{
string[] массивЖалоб = new string[5]
{
"О, зачем я пошел на эту специальность!",
"Как бы я был счастлив, если бы не C#!",
"Ну почему именно я и именно со мной!",
"Подвесить бы Уайта Гейтца за его язык C#!",
"Единственное спасение - уволить Снеткова!"
};
return массивЖалоб[ДатьЧисло(5)];
}
static void Main()
{
Жалобщик жалобы = new Жалобщик();
for(int i = 0; i < 10; i++)
{
Console.WriteLine("{0}) \t{1}",
i+1, жалобы.ГенераторЖалоб());
}
while(true);
}
}
}
Результаты выполнения не зависят от "культуры"
1) О, зачем я пошел на эту специальность! 2) Как бы я был счастлив, если бы не C#! 3) Единственное спасение - уволить Снеткова! 4) Как бы я был счастлив, если бы не C#! 5) Как бы я был счастлив, если бы не C#! 6) Подвесить бы Уайта Гейтца за его язык C#! 7) О, зачем я пошел на эту специальность! 8) Как бы я был счастлив, если бы не C#! 9) Единственное спасение - уволить Снеткова! 10) Подвесить бы Уайта Гейтца за его язык C#!
Предостережение
Никогда не используйте русские имена идентификаторов в программе. Иначе мозги лопнут если и не у Вас, так у бедного сопровождающего программиста - точно.
(методы уровня классов и методы уровня объектов; общие данные для всех объектов и индивидуальные данные для каждого объекта))
Если метод объявлен с ключевым словом static, то его нужно вызывать с именем самого класса. Более того, если статический метод для внутренних целей использует другие методы-члены и данные-члены класса, то они также должны быть объявлены в классе статическими. При этом статические методы уже нельзя будет вызывать применительно к экземплярам класса.
Если же ключевое слово static при объявлении метода опущено, то метод считается методом уровня объекта и его можно вызывать только с именем объекта. При этом будут обрабатываться данные, принадлежащие конкретному объекту как экземпляру класса.
Классы в C# могут содержать не только статические методы, но и статические данные-члены (
Приведем примеры...
using System;
namespace Test
{
class Teenager
{
// Объект для генерации случайных чисел
// rand - закрытая private переменная класса
static System.Random rand = new System.Random();
private static int GetRandomNumber(short upperLimit)
{
// Генерирует целое число в диапазоне
// от 0 до upperLimit
return rand.Next(upperLimit);
}
public static string Complain()
{
string[] messages = new string[5]
{
"О, зачем я пошел на эту специальность!",
"Как бы я был счастлив, если бы не C#!",
"Ну почему именно я и именно со мной!",
"Подвесить бы Уайта Гейтца за его язык C#!",
"Единственное спасение - уволить Снеткова!"
};
return messages[GetRandomNumber(5)];
}
static void Main()
{
for(int i = 0; i < 10; i++)
{
Console.WriteLine("{0}) \t{1}", i+1,
Teenager.Complain());
}
while(true);
}
}
}
Результаты выполнения
1) Подвесить бы Уайта Гейтца за его язык C#! 2) О, зачем я пошел на эту специальность! 3) Как бы я был счастлив, если бы не C#! 4) О, зачем я пошел на эту специальность! 5) О, зачем я пошел на эту специальность! 6) Ну почему именно я и именно со мной! 7) Ну почему именно я и именно со мной! 8) Как бы я был счастлив, если бы не C#! 9) Подвесить бы Уайта Гейтца за его язык C#! 10) Ну почему именно я и именно со мной!
Обратите внимание, что один из статических методов GetRandomNumber() мы скрыли - он нужен только для внутренних целей.
using System;
namespace Test
{
class Teenager
{
// Объект для генерации случайных чисел
// rand - закрытая private переменная класса
System.Random rand = new System.Random();
private int GetRandomNumber(short upperLimit)
{
// Генерирует целое число в диапазоне
// от 0 до upperLimit
return rand.Next(upperLimit);
}
public string Complain()
{
string[] messages = new string[5]
{
"О, зачем я пошел на эту специальность!",
"Как бы я был счастлив, если бы не C#!",
"Ну почему именно я и именно со мной!",
"Подвесить бы Уайта Гейтца за его язык C#!",
"Единственное спасение - уволить Снеткова!"
};
return messages[GetRandomNumber(5)];
}
static void Main()
{
Teenager boy = new Teenager();
for(int i = 0; i < 10; i++)
{
Console.WriteLine("{0}) \t{1}", i+1, boy.Complain());
}
while(true);
}
}
}
Результаты выполнения
1) Ну почему именно я и именно со мной! 2) Единственное спасение - уволить Снеткова! 3) Ну почему именно я и именно со мной! 4) Подвесить бы Уайта Гейтца за его язык C#! 5) Подвесить бы Уайта Гейтца за его язык C#! 6) Единственное спасение - уволить Снеткова! 7) Подвесить бы Уайта Гейтца за его язык C#! 8) О, зачем я пошел на эту специальность! 9) О, зачем я пошел на эту специальность! 10) Единственное спасение - уволить Снеткова!
Конструктор класса не может быть статическим, как и не должен возвращать никакого значения.
using System;
namespace Test
{
class CountObject
{
private static int countObject = 0;
public CountObject()// Конструктор
{
countObject++;
}
public int GetCountFromObject()
{
return countObject;
}
public static int GetCountFromClass()
{
return countObject;
}
static void Main()
{
CountObject ob1 = new CountObject();
CountObject ob2 = new CountObject();
CountObject ob3 = new CountObject();
CountObject ob4 = new CountObject();
CountObject ob5 = new CountObject();
Console.WriteLine("Создано объектов: {0}",
ob1.GetCountFromObject());
Console.WriteLine("Создано объектов: {0}",
ob2.GetCountFromObject());
Console.WriteLine("Создано объектов: {0}",
ob3.GetCountFromObject());
Console.WriteLine("Создано объектов: {0}",
ob4.GetCountFromObject());
Console.WriteLine("Создано объектов: {0}",
ob5.GetCountFromObject());
Console.WriteLine("\nСоздано объектов: {0}",
CountObject.GetCountFromClass());
while(true);
}
}
}
Результат примера
Создано объектов: 5 Создано объектов: 5 Создано объектов: 5 Создано объектов: 5 Создано объектов: 5 Создано объектов: 5
В классе System.Environment определено множество инструментов, в том числе
using System;
namespace Test
{
class Test
{
static void Main()
{
// Под какой OC мы работаем?
Console.WriteLine("Текущая OC: \t\t{0}",
Environment.OSVersion);
// Текущий каталог?
Console.WriteLine("Текущий каталог: \t{0}",
Environment.CurrentDirectory);
// Список логических дисков
Console.WriteLine("Список дисков:");
string[] drives = Environment.GetLogicalDrives();
for(int i = 0; i < drives.Length; i++)
Console.WriteLine("Диск {0}) \t\t{1}",
i+1, drives[i]);
// Какая версия платформы .NET у нас установлена?
Console.WriteLine("Текущая версия .NET: \t{0}",
Environment.Version);
while(true);
}
}
}
Результаты выполнения примера
Текущая OC: Microsoft Windows NT 5.0.2195.0 Текущий каталог: C:\Test\bin\Release Список логических дисков: Диск 1) A:\ Диск 2) C:\ Диск 3) D:\ Диск 4) E:\ Диск 5) F:\ Диск 6) Z:\ Текущая версия .NET: 1.1.4322.573
Программист может поручить компилятору контроль за параметрами методов, обозначив одни как входные, другие - как выходные. Для таких целей в C# предусмотрен ряд модификаторов, перечисленных в таблице
| Модификатор | Назначение |
|---|---|
(нет) - по умолчанию in ) |
Если модификатор никак не помечен, то по умолчанию считается, что это входящий параметр in для передачи переменной как значения. Вместо пропуска можно поставить in - результат будет таким же. |
out |
Маркирует возвращаемый методом параметр. |
ref |
Маркирует входной параметр, передаваемый по ссылке. Изменение параметра внутри метода означает изменение самих данных, хранящихся вне метода и адресуемых ссылкой. |
params |
Этот модификатор позволяет передавать целый набор параметров как единое целое. В любом методе может быть только один модификатор params и параметр с этим модификатором должен стоять последним в списке параметров метода. |
Проиллюстрируем прменение модификаторов на примере
using System;
namespace Test
{
class Test
{
//***************************************
public int Add(int x, int y)
{
return x + y;
}
//***************************************
public void Add(int x, int y, out int sum)
{
sum = x + y;
}
//***************************************
public void UpperCaseThisString(ref string str)
{
// Возвращает символьную строку с верхнем регистре
str = str.ToUpper();
}
//***************************************
public void DisplayArrayOfInt(string msg, params int[] list)
{
Console.Write(msg);
for(int i = 0; i < list.Length; i++)
Console.Write(" {0}", list[i]);// Вывод в строку
}
//***************************************
//***************************************
static void Main()
{
Test test = new Test();
Console.WriteLine("5 + 10 = {0}", test.Add(5, 10));
int sum; // Инициализировать необязательно - выходной
test.Add(50, 100, out sum);
Console.WriteLine("50 + 100 = {0}", sum);
string message = "\nПривет студентам от Снеткова!";
Console.WriteLine("Перед обработкой:{0}", message);
test.UpperCaseThisString(ref message);
Console.WriteLine("После обработки:{0}", message);
int[] arrayInt = new int[3]{1, 2, 3};
test.DisplayArrayOfInt("Вывод из массива целых:",
arrayInt);
test.DisplayArrayOfInt("\nВывод списка из трех целых:",
11, 22, 33);
test.DisplayArrayOfInt("\nВывод списка из пяти целых:",
55, 66, 77, 88, 99);
while(true);
}
}
}
Результат примера
5 + 10 = 15 50 + 100 = 150 Перед обработкой: Привет студентам от Снеткова! После обработки: ПРИВЕТ СТУДЕНТАМ ОТ СНЕТКОВА! Вывод из массива целых: 1 2 3 Вывод списка из трех целых: 11 22 33 Вывод списка из пяти целых: 55 66 77 88 99
Отметьте для себя, что модификаторы out и ref должны применяться и в определении метода и в его вызове, т.е. как при описании формальных параметров, так и фактических.
Можно передавать в качестве множественных параметров любые типы данных, если определить в списке
using System;
namespace Test
{
class Person
{
private string Name;
private int Age;
//***************************************
// Конструктор
public Person(string name, int age)
{
Name = name;
Age = age;
}
//***************************************
// Конструктор по умолчанию
public Person(){}
//***************************************
public void PrintInfo()
{
Console.WriteLine("Имя={0}; Возраст={1}", Name, Age);
}
}
//****************************************************************
class Test
{
//***************************************
// Что же мне пришлют на этот раз?
public void DisplayArrayOfObject(params object[] list)
{
for(int i = 0; i < list.Length; i++)
{
// Относится ли текущий объект из переданного
// массива объектов к классу Person?
if(list[i] is Person)
((Person)list[i]).PrintInfo();
else
Console.WriteLine(list[i]);
}
}
//***************************************
static void Main()
{
// Создали объект только
// для того, чтобы вызвать его метод
Test test = new Test();
// Создаем сотрудника
Person person = new Person("Студент
Вильгельм И.А.", 21);
// Передаем сотрудника и другие разнотипные объекты в нужном
// количестве методу, объявленному с модификатором params
test.DisplayArrayOfObject(
"Переменное число параметров любого типа",
"T 001 TT",
12345,
person,
Math.PI,
true,
"Привет студентам от Снеткова",
"..................... и т.д.");
while(true);
}
}
}
Результат примера
Переменное число параметров любого типа T 001 TT 12345 Имя=Студент Вильгельм И.А.; Возраст=21 3,14159265358979 True Привет студентам от Снеткова ..................... и т.д.
В языке C# нет операций адресации и разадресации и *, как это имеет место быть в C и C++. Вся передача обеспечивается использованием ссылок с явным или неявным использованием модификатора ref.
Массивы в C# - это набор однотипных элементов, доступ к которым производится по их числовому индексу. Наряду с массивами элементов простого встроенного типа (имеющим псевдонимы системных типов) существуют
Следует помнить, что размер массива задается при его создании, но не при объявлении. Если мы создаем new обязательно. Если при объявлении мы массив сразу инициализируем, то new необязательно - размерность массива определит компилятор. Индексация элементов массива начинается с нуля.
В C++ список инициализации может по количеству элементов не соответствовать заказанной C# заказанная
using System;
namespace Test
{
class Test
{
//***************************************
private void DisplayString(string subTitle, string[] list)
{
Console.WriteLine(subTitle);
for(int i = 0; i < list.Length; i++)
{
Console.WriteLine("{0}) {1}", i, list[i]);
}
}
//***************************************
private void DisplayInt(string subTitle, int[] list)
{
Console.WriteLine(subTitle);
for(int i = 0; i < list.Length; i++)
{
Console.WriteLine("{0}) {1}", i, list[i]);
}
}
//***************************************
static void Main()
{
string title = "************************
********************\n"
+ "\tОдномерные массивы:\n"
+ "*****************************
***************\n";
Console.WriteLine(title);
// Создадим объект для применения методов
// DisplayString() и DisplayInt()
Test test = new Test();
// Создадим ссылочную переменную типа массива с адресом null
string[] str1;
// Запросим память у системы для 10 элементов и запомним адрес.
str1 = new string[10];
test.DisplayString( "Строковый массив без
инициализации элементов.\n"
+ "Строковые элементы по умолчанию\n"
+ "инициализируются пустыми строками."
, str1);
// Поэлементная инициализация с использованием индексов
// и той же ссылки с новым адресом.
// Адрес старого массива потерян в куче. Утерянную
// память системе вернет сборщик мусора Garbare Collector
str1 = new string[7];
str1[0] = "Колян";
str1[1] = "Вован";
str1[2] = "Иван";
str1[3] = "Болван";
str1[4] = "Чурбан";
test.DisplayString("\nПоэлементная
инициализация с использованием индекса.\n"
+ "Старая адресуемая память утеряна
безвозвратно:", str1);
// Создадим ссылочную переменную и сразу инциализируем ее адресом.
int[] int1 = new int[5];
test.DisplayInt("\n"
+ "Числовой массив фиксированного размера.\n"
+ "Элементы по умолчанию инициализируются нулями."
, int1);
// Создаем ссылку на массив. Инициализируем ее адресом, запросив
// память с фиксированным количеством элементов,
// и сразу инициализируем элементы массива.
// Размер списка инициализации должен строго соответствовать
// запрошенной размерности массива.
int[] int2 = new int[5] {11, 22, 33, 44, 55};
test.DisplayInt(
"\n"
+ "Запрошенный размер массива должен\n"
+ "строго соответствовать списку инициализации."
, int2);
// Создаем ссылку на массив. Инициализируем ее адресом, запросив
// память без указания количества элементов. Количество элементов
// определит компилятор при инициализации списком.
// Используем оператор new.
int[] int3 = new int[] {11, 22, 33, 44, 55, 66, 77};
test.DisplayInt(
"\n"
+ "Размер по списку инициализации, \nпамять
запрашивается по new"
+ ":"
, int3);
// Создаем ссылку на массив. Инициализируем ее адресом, запросив
// память без указания количества элементов. Количество элементов
// определит компилятор при инициализации списком.
// Оператор new не используем.
int[] int4 = {11, 22, 33, 44, 55, 66, 77, 88, 99};
test.DisplayInt(
"\n"
+ "Размер по списку инициализации, \nпамять
запрашивается без new"
+ ":"
, int4);
while(true);
}
}
}
Результат выполнения примера
******************************************** Одномерные массивы: ******************************************** Строковый массив без инициализации элементов. Строковые элементы по умолчанию инициализируются пустыми строками. 0) 1) 2) 3) 4) 5) 6) 7) 8) 9) Поэлементная инициализация с использованием индекса. Старая адресуемая память утеряна безвозвратно: 0) Колян 1) Вован 2) Иван 3) Болван 4) Чурбан 5) 6) Числовой массив фиксированного размера. Элементы по умолчанию инициализируются нулями. 0) 0 1) 0 2) 0 3) 0 4) 0 Запрошенный размер массива должен строго соответствовать списку инициализации. 0) 11 1) 22 2) 33 3) 44 4) 55 Размер по списку инициализации, память запрашивается по new: 0) 11 1) 22 2) 33 3) 44 4) 55 5) 66 6) 77 Размер по списку инициализации, память запрашивается без new: 0) 11 1) 22 2) 33 3) 44 4) 55 5) 66 6) 77 7) 88 8) 99
Многомерные массивы могут иметь любое количество измерений (не путать с N измерений - N _мерные. Наибольшее распространение получили массивы, имеющие два измерения - двумерные многомерные массивы. Многомерные массивы (в том числе двумерные) бывают двух типов:
Ниже рассмотрим примеры, касающиеся только двумерных массивов.
using System;
namespace Test
{
class Test
{
static void Main()
{
string title = "*****************************
***************\n"
+ "\tДвумерные массивы:\n"
+ "****************************
****************\n";
Console.WriteLine(title);
//*********************************************************
// Создаем ссылку типа int на двумерный массив
// Используются для измерений общие скобки
int[,] array1;
// Запросим память у системы для
// Для фиксированного количества элементов
array1 = new int[6,5];
Console.WriteLine("Прямоугольный массив [6,5],
полученный по\n"
+ "синтаксису массива прямоугольного типа:");
// Заполним и выведем массив с поэлементной индексацией
// Метод GetLength() используется для определения
// количества элементов в указанном измерении массива
for(int i = 0; i < array1.GetLength(0); i++)
{
for(int j = 0; j < array1.GetLength(1); j++)
{
array1[i,j] = i * j;
Console.Write(array1[i,j] + "\t");
}
Console.WriteLine();// На новую строку экрана
}
//*********************************************************
// Используются для измерений индивидуальные скобки
int[][] array2;
// Запросим память у системы для
// Для неопределенного количества элементов
// по второму измерению
array2 = new int[6][];
Console.WriteLine("\n\nПрямоугольный массив [6][5],
полученный по\n"
+ "синтаксису массива ломаного типа:");
// Вначале запросим память для второго измерения
// Используются для измерения индивидуальные скобки
for(int i = 0; i < array2.Length; i++)
array2[i] = new int[5];
// Заполним и выведем массив с поэлементной индексацией
for(int i = 0; i < 6; i++)
{
for(int j = 0; j < 5; j++)
{
array2[i][j] = i * j;
Console.Write(array2[i][j] + "\t");
}
Console.WriteLine();// На новую строку экрана
}
//*********************************************************
// Используются для измерений индивидуальные скобки
// Запросим память у системы для
// Для неопределенного количества элементов
// по второму измерению
array2 = new int[6][]; // Используем старую ссылку
// массива ломаного типа, старую ссылку
// массива прямоугольного типа применять нельзя
Console.WriteLine("\n\nМассив ломаного
типа [6][]:");
// Вначале запросим память для второго измерения
// Используются для измерения индивидуальные скобки
for(int i = 0; i < array2.Length; i++)
array2[i] = new int[i + 2];
// Заполним и выведем массив с поэлементной индексацией
for(int i = 0; i < array2.Length; i++)
{
for(int j = 0; j < array2[i].Length; j++)
{
array2[i][j] = i * j;
Console.Write(array2[i][j] + "\t");
}
Console.WriteLine();// На новую строку экрана
}
while(true);
}
}
}
Результат выполнения примера
******************************************** Двумерные массивы: ******************************************** Прямоугольный массив [6,5], полученный по синтаксису массива прямоугольного типа: 0 0 0 0 0 0 1 2 3 4 0 2 4 6 8 0 3 6 9 12 0 4 8 12 16 0 5 10 15 20 Прямоугольный массив [6][5], полученный по синтаксису массива ломаного типа: 0 0 0 0 0 0 1 2 3 4 0 2 4 6 8 0 3 6 9 12 0 4 8 12 16 0 5 10 15 20 Массив ломаного типа [6][]: 0 0 0 1 2 0 2 4 6 0 3 6 9 12 0 4 8 12 16 20 0 5 10 15 20 25 30
Все массивы в C# являются производными от базового класса System.Array. За счет этого любой массив наследует множество методов и свойств, которые сильно упрощают работу программиста. Методы можно увидеть в суфлере кода IntelliSense, применив его к имени класса или конкретному экземпляру.
![]() |
![]() |
| Член класса | Назначение |
|---|---|
|
Этот статический метод можно использовать только тогда, когда массив реализует интерфейс ICompare. Если этот интерфейс реализован, то данный метод позволяет найти элемент массива. |
Clear() |
Этот статический метод позволяет очистить указанный диапазон элементов и инициализировать их "безопасными" значениями. Массив целых получит нулевые значения элементов, строковый - пустые строки, ссылки преобретут значения null. |
CopyTo() |
Используется для копирования элементов из исходного массива в массив назначения. |
GetEnumerator() |
Возвращает интерфейс для указанного массива. Этот интерфейс необходим для применения конструкции цикла foreach. |
GetLength(), Length |
Метод GetLength() возвращает количество элементов для заданного в качестве аргумента измерения массива. Нумерация измерений начинается с нуля. Length - это свойство только для чтения, содержащее общее количество элементов массива. |
GetLowerBound(), GetUpperBound() |
Эти методы используются для определения нижней и верхней границ выбранного измерения массива. |
GetValue(), SetValue() |
Возвращает или устанавливает значение элемента по указанному в качестве аргумента индексу. Этот метод |
Reverse() |
Этот статический метод изменяет порядок следования элементов массива на противоположный. |
Sort() |
Сортирует одномерный массив IComparer, то с помощью этого метода можно сортировать и данные пользовательских типов. |
Рассмотрим примеры
using System;
namespace Test
{
class Test
{
private void DisplayString(string title, string[] list)
{
Console.WriteLine(title);
for(int i = 0; i < list.Length; i++)
Console.Write(list[i] + "\t");
Console.WriteLine(); // Для перевода строки
}
static void Main()
{
// Создадим объект для применения метода DisplayString()
Test test = new Test();
string title = "********************
************************\n"
+ "\tМетоды класса System.Array:\n"
+ "**************************
******************\n";
Console.WriteLine(title);
// Создаем строковый массив
string[] firstNames = new string[5]
{
"Колян",
"Вован",
"Иван",
"Болван",
"Чурбан"
};
test.DisplayString("Исходный массив:", firstNames);
// Расставим элементы в обратном порядке
// при помощи статического метода System.Array
Array.Reverse(firstNames);
test.DisplayString("\nИнверсный массив:
", firstNames);
// Сортируем элементы по алфавиту
// при помощи статического метода System.Array
Array.Sort(firstNames);
test.DisplayString("\nСортированный
массив:", firstNames);
// Очищаем элементы массива, начиная с имеющего
// индекс 1 три стоящих подряд
// при помощи статического метода System.Array
Array.Clear(firstNames, 1, 3);
test.DisplayString("\nЧастично очищенный массив:
", firstNames);
while(true);
}
}
}
Результат выполнения примера
******************************************** Методы класса System.Array: ******************************************** Исходный массив: Колян Вован Иван Болван Чурбан Инверсный массив: Чурбан Болван Иван Вован Колян Сортированный массив: Болван Вован Иван Колян Чурбан Частично очищенный массив: Болван Чурбан
Встроенный тип string (строки Unicode ) относится к System.String. Этот базовый класс обеспечивает программиста множеством полезных свойств и методов для обработки строк. Часть методов является методами объектов, часть - методами класса (статическими).
| Член класса | Назначение |
|---|---|
Length |
Это свойство возвращает длину указанной строки |
Concat() |
Этот статический метод возвращает новую строку, склеенную из двух исходных |
CompareTo() |
Сравнивает две строки |
Copy() |
Этот статический метод создает новую копию существующей строки |
Format() |
Используется для форматирования строки с использованием спецификаторов форматирования и подстановочных выражений вида {0} |
Insert() |
Используется для вставки строки внутрь существующей |
PadLeft() PadRight() |
Для заполнения строки слева или справа указанными символами |
Remove() Replace() |
Эти методы позволяют создать копию строки с внесенными изменениями (частично удаленными или замененными символами) |
ToUpper() ToLower() |
Возвращают копию строки с символами в верхнем или нижнем регистре |
Для этого типа некоторые операции перегружены так, что позволяют работать со значениями, а не со ссылками. Например, при использовании операторов равенства == или неравенства != происходит сравнение значений строковых объектов, а не адресов этих объектов в оперативной памяти. Оператор сложения + перегружен так, что при его применении к строковым объектам вызывается неявно метод Concat(). Кроме того, можно обратиться к любому символу строки с помощью оператора индекса [].
using System;
namespace Test
{
class Test
{
static void Main()
{
string title = "******************
**************************\n"
+ "\tМетоды класса System.String\n"
+ "************************
********************\n";
Console.WriteLine(title);
// Создаем строку по классу
System.String strObject = "Это строка-экземпляр
класса System.String:";
string strAlias = "Это строка-экземпляр
встроенного псевдонима string:";
// Сравниваем содержимое
if(strObject == strAlias)
Console.WriteLine("Строки равны");
else
Console.WriteLine("Строки неравны");
// Склеиваем строки
string addString = strObject + strAlias;
Console.WriteLine("\nСклеенные строки\n{0}",
addString);
// Применяем индексатор для доступа к символам строки
Console.WriteLine("\nСимволы строки \"
{0}\"", strAlias);
for(int i = 0; i < strAlias.Length; i++)
Console.WriteLine("{0}. {1}", i,
"\t" + strAlias[i]);
while(true);
}
}
}
Результат выполнения примера
******************************************** Методы класса System.String ******************************************** Строки неравны Склеенные строки Это строка-экземпляр класса System.String:Это строка-экземпляр встроенного псевдонима string: Символы строки "Это строка-экземпляр встроенного псевдонима string:" 0. Э 1. т 2. о 3. 4. с 5. т 6. р 7. о 8. к 9. а 10. - 11. э 12. к 13. з 14. е 15. м 16. п 17. л 18. я 19. р 20. 21. в 22. с 23. т 24. р 25. о 26. е 27. н 28. н 29. о 30. г 31. о 32. 33. п 34. с 35. е 36. в 37. д 38. о 39. н 40. и 41. м 42. а 43. 44. s 45. t 46. r 47. i 48. n 49. g 50. :
В C# строки могут содержать любое количество
| Управляющий символ | Назначение |
|---|---|
@ |
Префикс дословного вывода строк ( verbatim strings ). Ставится перед формируемой строкой и отменяет срабатывание всех управляющих символов |
\' |
Вставить одинарную кавычку |
\" |
Вставить двойную кавычку |
\\ |
Вставить обратный слэш |
{{ |
Вставить фигурную открывающую скобку |
}} |
Вставить фигурную закрывающую скобку |
\a |
Запустить системное звуковое оповещение ( Alert ) |
\b |
Вернуться на одну позицию ( Backspace ) |
\f |
Начать следующую страницу ( ) |
\n |
Вставить новую строку ( New line ) |
\r |
Вставить возврат каретки ( ) |
\t |
Вставить горизонатльный символ табуляции ( horizontal Tab ) |
\u |
Вставить символ Unicode |
\v |
Вставить вертикальный символ табуляции ( Vertical tab ) |
\o |
Представляет нулевой символ NULL (все биты представления нулевые) |
using System;
namespace Test
{
class Test
{
static void Main()
{
// Форматирование без параметров + Звукнем
string str = String.Format("\aЯ передаю \"
Привет всем!\" {{Снетков}}\n");
Console.WriteLine(str);
while(true);
}
}
}
Результат выполнения примера
Я передаю "Привет всем!" {Снетков}
При работе со строками в C# первоначальное значение строки не может быть изменено. Мы применяем к строкам множество модифицирующих методов и получаем нужный результат, но на самом деле методы каждое изменение строки записывают на новом месте в куче, а не используют одну и ту же область памяти. Ссылки на старые места хранения строк безжалостно бросаются, а методы возвращают лишь копии измененного содержимого, размещенного на новом месте. Брошенные ссылки подберет позже сборщик мусора, когда будет отдавать системе уже неадресуемые места памяти.
Когда потребуется не создавать лишние копии строк (особенно для больших объемов данных), а обрабатывать строки напрямую в месте их хранения, можно воспользоваться классом StringBuilder в пространстве имен System.Text.
Создание объекта класса StringBuilder осуществляется при помощи одного из многих его перегруженных конструкторов. Параметры конструкторов позволяют задавать значения четырех свойств, приведенных в таблице.
| Свойство | Назначение |
|---|---|
Capacity
(емкость) |
Свойство определяет исходный размер элемента символьного массива в экземпляре класса StringBuilder. По умолчанию принято значение 16. Если существующий размер меньше, чем необходимо для такой операции как добавление, то объект класса StringBuilder удваивает размер символьного массива. Поскольку такие операции снижают производительность, рекомендуется заранее задавать необходимый размер, если он известен на момент создания объекта класса StringBuilder. |
Length
(длина) |
Длина строки в данном экземпляре объекта класса StringBuilder. |
MaxCapacity
(максимальная емкость) |
Свойство задает максимальное количество символов, которое может содержать текущий экземпляр класса StringBuilder. По умолчанию принято значение Int32.. Свойство StringBuilder.MaxCapacity уже созданного объекта не может быть изменено. |
Chars
(символы) |
Это индексируемое свойство внутреннего символьного массива, входящего в объект StringBuilder. Индексы этого свойства начинаются с нуля. |
| Метод | Назначение |
|---|---|
Append() |
Добавляет строковое ( System.String ) представление указанного объекта к содержимому экземпляра класса StringBuilder |
AppendFormat() |
Подобен методу Append(), подразумевает применение спецификаторов форматирования и национальных особенностей. |
EnsureCapacity() |
Гарантирует определенную емкость символьного массива. |
Equals() |
Сранивает два экземпляра класса StringBuilder на совпадение значений всех свойств и содержимого символьного массива. |
Insert() |
Вставляет строковое представление указанного объекта в определенную позицию символьного массива экземпляра StringBuilder |
Remove() |
Удаляет определенный |
Replace() |
Заменяет в объекте указанные вхождения строк на другие |
ToString() |
Преобразует допускающий изменения экземпляр класса System.Text.StringBuilder в неизменный экземпляр класса System.String |
using System; // Для класса String
using System.Text; // Для класса StringBuilder
namespace Test
{
class Test
{
static void Main()
{
String strFixed = new String(
"Эта исходная строка не меняется".ToCharArray());
String strUpper = strFixed.ToUpper();
Console.WriteLine(strFixed);
Console.WriteLine(strUpper);// Новая копия
int capacity = 100; // Исходный размер
StringBuilder myBuffer = new StringBuilder(
"\nИсходная строка", capacity);
myBuffer.Append("+Добавленная строка");
Console.WriteLine(myBuffer);// Та же строка
Console.ReadLine();
}
}
}
Результат выполнения примера
Эта исходная строка не меняется ЭТА ИСХОДНАЯ СТРОКА НЕ МЕНЯЕТСЯ Исходная строка+Добавленная строка
using System;
namespace Test
{
enum EmpType
{
Manager,
Grunt,
Contractor,
VP
}
class Test
{
void Variant(EmpType en)
{
switch(en)
{
case EmpType.Contractor:
Console.WriteLine("Работает по контракту");
break;
case EmpType.Grunt:
Console.WriteLine("");
break;
case EmpType.Manager:
Console.WriteLine("");
break;
case EmpType.VP:
Console.WriteLine("");
break;
default:
Console.WriteLine("");
break;
}
}
static void Main()
{
Console.ReadLine();
}
}
}
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.