Разработка приложений на C# в среде Visual Studio 2005

Консольные приложения в С#

Разбить на страницы
Показывать лекцию целиком

Структура класса C#

Компания Microsoft специально для платформы .NET Framework разработала язык C# (Си-шарп). В отличие от языка 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() являются параметры командной строки, передаваемые в период запуска программы. Мы вольны использовать их по своему усмотрению или игнорировать. Могут быть следующие вариации заголовка метода Main()

    Варианты метода 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 Collector ). Сборщик мусора платформы . 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#

    В C# различают структурные типы ( reference-based ) и ссылочные ( value-based ). Переменная структурного типа представляет закрепленное за ней само значение оперативной памяти. Переменная ссылочного типа содержит лишь адрес области оперативной памяти с размещаемым в этой области значением. Все структурные переменные размещаются на стеке, а ссылочные - на управляемой куче ( managed heap ). К структурным типам относятся перечисления и структуры, а к ссылочным - классы, массивы, указатели, интерфейсы.

    Когда выполняется присваивание одной структурной переменной другой, то создается побитовая копия данных, которые размещены в разных областях стековой памяти и существуют независимо друг от друга. При выполнении присваивания ссылочных переменных создается копия адреса, после чего обе переменные будут ссылаться на одну и ту же область памяти на управляемой куче. То есть создается как-бы псевдонимы одной и той же области памяти. Это означает, что изменения объекта по одной ссылочной переменной равноценно изменениям по псевдониму

  • Создайте консольное приложение 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 Collector (сборщик мусора)
    Можно ли напрямую инициализировать данные при объявлении типа Нет Да
    Можно ли перегружать конструктор по умолчанию Нет, он зарезервирован за системой Да, и обязательно, если определен хоть один общий конструктор

    Несмотря на все различия, структурные и ссылочные типы могут иметь и поддерживать как встроенные (стандартные), так и определенные пользователем (программистом) методы, свойства, события в необходимом количестве. Но удобно за структурными типами закреплять только поддержку сложных данных.

    Продемонстрируем сказанное простым примером.

  • Внесите следующие изменения в файл Test.cs консольного приложения C# с именем Test
  • using 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

    Базовый тип System.Object

    Все типы в 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();
    }
    }

    Предназначение каждого метода приведено в таблице

    Главные методы базового библиотечного класса System.Object
    Метод Описание
    Equals() Возвращает true, если сравниваемые сущности занимают одну и ту же область памяти. По умолчанию настроен для сравнивания объектов ссылочных типов. Для применения к объектам структурных типов этот метод и сопутствующий ему метод GetHashCode() нужно перегрузить своими методами.
    GetHashCode() Возвращает целочисленное значение ID, идентифицирующее конкретный экземпляр объекта данного типа. Генерирует уникальный хэш.
    GetType() Метод возвращает объект Type(), полностью описывающий тот объект, из которого этот метод был вызван. Это метод идентификации времени выполнения ( Runtime Type Identification - RTTI ) , который предусмотрен во всех объектах.
    ToString() Возвращает символьное представление объекта в формате "путь_пространства_имен.имя_класса". Такой формат называется fully qualified name - полностью определенное имя. Если тип определен вне какого-либо пространства имен, возвращается только имя класса без именного пути. Этот метод может быть перегружен для предоставления информации о внутреннем состоянии объекта в формате имя-значение.
    Finalize() Основное назначение этого метода - освободить все ресурсы, занятые объектом данного класса, перед его удалением для корректной работы сборщика мусора gc - Garbare Collector.
    MemberwiseClone() Этот метод предназначен для создания еще одной ссылки на область, занимаемую объектом данного типа в оперативной памяти. Этот метод не может быть перегружен. Если требуется реализовать поддержку создания полной копии объекта в оперативной памяти, то необходимо реализовать поддержку интерфейса ICloneable

    В следующем примере продемонстрированы возможности некотороых из перечисленных методов

    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 
    Адресация памяти совпадает

    Перегрузка стандартных методов класса System.Object

    Перегружать в производном классе можно только методы, объявленные в базовом классе как виртуальные с префиксом virtual. Перегружаемый метод должен маркироваться ключевым словом override.

    Методы, которые наследуются типами от базового класса System.Object, иногда могут не устраивать пользователя (программиста) при разработке своих типов. Поэтому может возникнуть необходимость перегружать некоторые из них. Покажем это на примерах.

    Перегрузка (замещение) виртуального метода ToString()

    Разработаем класс 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

    Статические методы класса System.Object

    Мы испробовали работу метода 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#

    Любой встроенный в C# тип данных - это лишь псевдоним для существующего типа, определенного в пространстве имен System. В таблице приведены все системные типы данных и соответствующие им псевдонимы C#, а также информация о совместимости с общей средой выполнения CLR (Common Language Runtime). Совместимость обеспечивается правилами, установленными CLS (Common Language Specification).

    Системные типы данных и их псевдонимы в C#
    Псевдоним 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 -совместимой. Если мы создаем приложение, которое должно работать в многоязыковой среде, то желательно ограничиться только CLS -совместимыми типами. Этим мы гарантируем, что наши классы, интерфейсы и структуры будут нормально работать с любым языком . 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);
    }
    }
    }

    Результат выполнения примера

    Значения одинаковые 
    Ссылки различаются

    Системные типы как классы имеют свои методы и свойства

    Отметим удобные свойства MaxValue и MinValue, которые содержат пределы поддерживаемого данным типом числового диапазона. Составим для иллюстрации небольшой тест для типа 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); // Здесь поставим точку останова для отладчика
    }
      }
    }

    После останова программы в точке останова Breakpoint выполните последовательность нажатий клавиш < 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#

    Для организации ввода-вывода в 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#

    В C++ в консольном выводе функции prinf() применяются спецификаторы форматирования %d, %s, %c и т.д. в отдельности для каждого встроенного типа данных. В C# в консольном выводе функций Console.Write() и Console.Writeln() применяются универсальные индексированные метки {0}, {1}, {2}, и т.д., содержащие порядковый номер выводимого параметра в списке параметров. Эти подстановочные выражения обозначают знакоместа ( placeholder ), куда будет выводиться значение соответствующего параметра из списка.

    Знакоместо должно начинаться открывающей фигурной скобкой, а завершаться закрывающей {}. Чтобы отобразить содержащиеся в строке форматирования фигурные скобки на дисплее, их следует указать парно {{ или }}. Аналогично, чтобы вывести на дисплей символ обратной косой черты, его нужно заэкранировать в строке форматирования вторым таким же символом.

    Параметры форматирования можно применять и для массивов. При этом подстановочные выражения будут указывать на элементы этого массива.

    В подстановочном выражении можно указывать и более точные настройки формата с помощью дополнительных параметров форматирования. Параметры форматирования приведены в таблице, причем их можно указывать как строчными так и прописными буквами.

    Параметры форматирования C#
    Параметр Значение
    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#

    Метод - это способ упаковки кода, который рассматривается как единое целое и может быть выполнен в процессе работы программы. В C# не существует глобальных методов - любой метод обязательно должен быть членом класса или структуры.

    Методы могут принимать или не принимать параметры, могут возвращать или не возвращать значения, могут быть методами класса (статическими) или методами экземпляра (объекта).

    Для каждого метода существует свой уровень доступа, который определяет, откуда можно будет обратиться к этому методу. Для указания уровня доступа существуют специальные модификаторы. Они приведены в таблице

    Модификаторы уровня доступа к методам в 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

    Некоторые полезные статические члены класса Environment (среда)

    В классе 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# предусмотрен ряд модификаторов, перечисленных в таблице

    Модификаторы для параметров методов 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#

    Массивы в 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 _мерные. Наибольшее распространение получили массивы, имеющие два измерения - двумерные многомерные массивы. Многомерные массивы (в том числе двумерные) бывают двух типов:

  • Прямоугольные массивы
  • "Ломаные" (jagged) массивы
  • Ниже рассмотрим примеры, касающиеся только двумерных массивов.

    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

    Базовый класс System.Array

    Все массивы в C# являются производными от базового класса System.Array. За счет этого любой массив наследует множество методов и свойств, которые сильно упрощают работу программиста. Методы можно увидеть в суфлере кода IntelliSense, применив его к имени класса или конкретному экземпляру.

    Некоторые члены класса System.Array
    Член класса Назначение
    BinarySearch() Этот статический метод можно использовать только тогда, когда массив реализует интерфейс ICompare. Если этот интерфейс реализован, то данный метод позволяет найти элемент массива.
    Clear() Этот статический метод позволяет очистить указанный диапазон элементов и инициализировать их "безопасными" значениями. Массив целых получит нулевые значения элементов, строковый - пустые строки, ссылки преобретут значения null.
    CopyTo() Используется для копирования элементов из исходного массива в массив назначения.
    GetEnumerator() Возвращает интерфейс IEnumerator для указанного массива. Этот интерфейс необходим для применения конструкции цикла 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:
    ******************************************** 
      
    Исходный массив:
    Колян  Вован  Иван  Болван  Чурбан
      
    Инверсный массив:
    Чурбан  Болван  Иван  Вован  Колян
      
    Сортированный массив:
    Болван  Вован  Иван  Колян  Чурбан
      
    Частично очищенный массив:
    Болван        Чурбан

    Работа со строками в C#

    Встроенный тип string (строки Unicode ) относится к ссылочному типу данных и является псевдонимом класса System.String. Этот базовый класс обеспечивает программиста множеством полезных свойств и методов для обработки строк. Часть методов является методами объектов, часть - методами класса (статическими).

    Некоторые члены класса 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# строки могут содержать любое количество управляющих последовательностей ( escape characters - управляющих символов), на которые реагируют средства вывода строк.

    Управляющие последовательности
    Управляющий символ Назначение
    @ Префикс дословного вывода строк ( verbatim strings ). Ставится перед формируемой строкой и отменяет срабатывание всех управляющих символов
    \' Вставить одинарную кавычку
    \" Вставить двойную кавычку
    \\ Вставить обратный слэш
    {{ Вставить фигурную открывающую скобку
    }} Вставить фигурную закрывающую скобку
    \a Запустить системное звуковое оповещение ( Alert )
    \b Вернуться на одну позицию ( Backspace )
    \f Начать следующую страницу ( Form feed )
    \n Вставить новую строку ( New line )
    \r Вставить возврат каретки ( carriage Return )
    \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);
      }
    }
    }

    Результат выполнения примера

    Я передаю "Привет всем!" {Снетков}

    Применение класса System.Text.StringBuilder

    При работе со строками в C# первоначальное значение строки не может быть изменено. Мы применяем к строкам множество модифицирующих методов и получаем нужный результат, но на самом деле методы каждое изменение строки записывают на новом месте в куче, а не используют одну и ту же область памяти. Ссылки на старые места хранения строк безжалостно бросаются, а методы возвращают лишь копии измененного содержимого, размещенного на новом месте. Брошенные ссылки подберет позже сборщик мусора, когда будет отдавать системе уже неадресуемые места памяти.

    Когда потребуется не создавать лишние копии строк (особенно для больших объемов данных), а обрабатывать строки напрямую в месте их хранения, можно воспользоваться классом StringBuilder в пространстве имен System.Text.

    Создание объекта класса StringBuilder осуществляется при помощи одного из многих его перегруженных конструкторов. Параметры конструкторов позволяют задавать значения четырех свойств, приведенных в таблице.

    Свойства, задаваемые через параметры конструкторов StringBuilder при создании объекта
    Свойство Назначение
    Capacity (емкость) Свойство определяет исходный размер элемента символьного массива в экземпляре класса StringBuilder. По умолчанию принято значение 16. Если существующий размер меньше, чем необходимо для такой операции как добавление, то объект класса StringBuilder удваивает размер символьного массива. Поскольку такие операции снижают производительность, рекомендуется заранее задавать необходимый размер, если он известен на момент создания объекта класса StringBuilder.
    Length (длина) Длина строки в данном экземпляре объекта класса StringBuilder.
    MaxCapacity (максимальная емкость) Свойство задает максимальное количество символов, которое может содержать текущий экземпляр класса StringBuilder. По умолчанию принято значение Int32.MaxValue. Свойство StringBuilder.MaxCapacity уже созданного объекта не может быть изменено.
    Chars (символы) Это индексируемое свойство внутреннего символьного массива, входящего в объект StringBuilder. Индексы этого свойства начинаются с нуля.

    Методы класса 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();
      }
    }
    }

    Результат выполнения примера

    Эта исходная строка не меняется
    ЭТА ИСХОДНАЯ СТРОКА НЕ МЕНЯЕТСЯ
      
    Исходная строка+Добавленная строка

    Перечисления в C#

    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();
      }
    }
    }
    Страницы:

    Структура класса C#

    Компания Microsoft специально для платформы .NET Framework разработала язык C# (Си-шарп). В отличие от языка 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() являются параметры командной строки, передаваемые в период запуска программы. Мы вольны использовать их по своему усмотрению или игнорировать. Могут быть следующие вариации заголовка метода Main()

    Варианты метода 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 Collector ). Сборщик мусора платформы . 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#

    В C# различают структурные типы ( reference-based ) и ссылочные ( value-based ). Переменная структурного типа представляет закрепленное за ней само значение оперативной памяти. Переменная ссылочного типа содержит лишь адрес области оперативной памяти с размещаемым в этой области значением. Все структурные переменные размещаются на стеке, а ссылочные - на управляемой куче ( managed heap ). К структурным типам относятся перечисления и структуры, а к ссылочным - классы, массивы, указатели, интерфейсы.

    Когда выполняется присваивание одной структурной переменной другой, то создается побитовая копия данных, которые размещены в разных областях стековой памяти и существуют независимо друг от друга. При выполнении присваивания ссылочных переменных создается копия адреса, после чего обе переменные будут ссылаться на одну и ту же область памяти на управляемой куче. То есть создается как-бы псевдонимы одной и той же области памяти. Это означает, что изменения объекта по одной ссылочной переменной равноценно изменениям по псевдониму

  • Создайте консольное приложение 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 Collector (сборщик мусора)
    Можно ли напрямую инициализировать данные при объявлении типа Нет Да
    Можно ли перегружать конструктор по умолчанию Нет, он зарезервирован за системой Да, и обязательно, если определен хоть один общий конструктор

    Несмотря на все различия, структурные и ссылочные типы могут иметь и поддерживать как встроенные (стандартные), так и определенные пользователем (программистом) методы, свойства, события в необходимом количестве. Но удобно за структурными типами закреплять только поддержку сложных данных.

    Продемонстрируем сказанное простым примером.

  • Внесите следующие изменения в файл Test.cs консольного приложения C# с именем Test
  • using 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

    Базовый тип System.Object

    Все типы в 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();
    }
    }

    Предназначение каждого метода приведено в таблице

    Главные методы базового библиотечного класса System.Object
    Метод Описание
    Equals() Возвращает true, если сравниваемые сущности занимают одну и ту же область памяти. По умолчанию настроен для сравнивания объектов ссылочных типов. Для применения к объектам структурных типов этот метод и сопутствующий ему метод GetHashCode() нужно перегрузить своими методами.
    GetHashCode() Возвращает целочисленное значение ID, идентифицирующее конкретный экземпляр объекта данного типа. Генерирует уникальный хэш.
    GetType() Метод возвращает объект Type(), полностью описывающий тот объект, из которого этот метод был вызван. Это метод идентификации времени выполнения ( Runtime Type Identification - RTTI ) , который предусмотрен во всех объектах.
    ToString() Возвращает символьное представление объекта в формате "путь_пространства_имен.имя_класса". Такой формат называется fully qualified name - полностью определенное имя. Если тип определен вне какого-либо пространства имен, возвращается только имя класса без именного пути. Этот метод может быть перегружен для предоставления информации о внутреннем состоянии объекта в формате имя-значение.
    Finalize() Основное назначение этого метода - освободить все ресурсы, занятые объектом данного класса, перед его удалением для корректной работы сборщика мусора gc - Garbare Collector.
    MemberwiseClone() Этот метод предназначен для создания еще одной ссылки на область, занимаемую объектом данного типа в оперативной памяти. Этот метод не может быть перегружен. Если требуется реализовать поддержку создания полной копии объекта в оперативной памяти, то необходимо реализовать поддержку интерфейса ICloneable

    В следующем примере продемонстрированы возможности некотороых из перечисленных методов

    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 
    Адресация памяти совпадает

    Перегрузка стандартных методов класса System.Object

    Перегружать в производном классе можно только методы, объявленные в базовом классе как виртуальные с префиксом virtual. Перегружаемый метод должен маркироваться ключевым словом override.

    Методы, которые наследуются типами от базового класса System.Object, иногда могут не устраивать пользователя (программиста) при разработке своих типов. Поэтому может возникнуть необходимость перегружать некоторые из них. Покажем это на примерах.

    Перегрузка (замещение) виртуального метода ToString()

    Разработаем класс 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

    Статические методы класса System.Object

    Мы испробовали работу метода 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#

    Любой встроенный в C# тип данных - это лишь псевдоним для существующего типа, определенного в пространстве имен System. В таблице приведены все системные типы данных и соответствующие им псевдонимы C#, а также информация о совместимости с общей средой выполнения CLR (Common Language Runtime). Совместимость обеспечивается правилами, установленными CLS (Common Language Specification).

    Системные типы данных и их псевдонимы в C#
    Псевдоним 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 -совместимой. Если мы создаем приложение, которое должно работать в многоязыковой среде, то желательно ограничиться только CLS -совместимыми типами. Этим мы гарантируем, что наши классы, интерфейсы и структуры будут нормально работать с любым языком . 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);
    }
    }
    }

    Результат выполнения примера

    Значения одинаковые 
    Ссылки различаются

    Системные типы как классы имеют свои методы и свойства

    Отметим удобные свойства MaxValue и MinValue, которые содержат пределы поддерживаемого данным типом числового диапазона. Составим для иллюстрации небольшой тест для типа 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); // Здесь поставим точку останова для отладчика
    }
      }
    }

    После останова программы в точке останова Breakpoint выполните последовательность нажатий клавиш < 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#

    Для организации ввода-вывода в 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#

    В C++ в консольном выводе функции prinf() применяются спецификаторы форматирования %d, %s, %c и т.д. в отдельности для каждого встроенного типа данных. В C# в консольном выводе функций Console.Write() и Console.Writeln() применяются универсальные индексированные метки {0}, {1}, {2}, и т.д., содержащие порядковый номер выводимого параметра в списке параметров. Эти подстановочные выражения обозначают знакоместа ( placeholder ), куда будет выводиться значение соответствующего параметра из списка.

    Знакоместо должно начинаться открывающей фигурной скобкой, а завершаться закрывающей {}. Чтобы отобразить содержащиеся в строке форматирования фигурные скобки на дисплее, их следует указать парно {{ или }}. Аналогично, чтобы вывести на дисплей символ обратной косой черты, его нужно заэкранировать в строке форматирования вторым таким же символом.

    Параметры форматирования можно применять и для массивов. При этом подстановочные выражения будут указывать на элементы этого массива.

    В подстановочном выражении можно указывать и более точные настройки формата с помощью дополнительных параметров форматирования. Параметры форматирования приведены в таблице, причем их можно указывать как строчными так и прописными буквами.

    Параметры форматирования C#
    Параметр Значение
    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#

    Метод - это способ упаковки кода, который рассматривается как единое целое и может быть выполнен в процессе работы программы. В C# не существует глобальных методов - любой метод обязательно должен быть членом класса или структуры.

    Методы могут принимать или не принимать параметры, могут возвращать или не возвращать значения, могут быть методами класса (статическими) или методами экземпляра (объекта).

    Для каждого метода существует свой уровень доступа, который определяет, откуда можно будет обратиться к этому методу. Для указания уровня доступа существуют специальные модификаторы. Они приведены в таблице

    Модификаторы уровня доступа к методам в 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

    Некоторые полезные статические члены класса Environment (среда)

    В классе 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# предусмотрен ряд модификаторов, перечисленных в таблице

    Модификаторы для параметров методов 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#

    Массивы в 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 _мерные. Наибольшее распространение получили массивы, имеющие два измерения - двумерные многомерные массивы. Многомерные массивы (в том числе двумерные) бывают двух типов:

  • Прямоугольные массивы
  • "Ломаные" (jagged) массивы
  • Ниже рассмотрим примеры, касающиеся только двумерных массивов.

    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

    Базовый класс System.Array

    Все массивы в C# являются производными от базового класса System.Array. За счет этого любой массив наследует множество методов и свойств, которые сильно упрощают работу программиста. Методы можно увидеть в суфлере кода IntelliSense, применив его к имени класса или конкретному экземпляру.

    Некоторые члены класса System.Array
    Член класса Назначение
    BinarySearch() Этот статический метод можно использовать только тогда, когда массив реализует интерфейс ICompare. Если этот интерфейс реализован, то данный метод позволяет найти элемент массива.
    Clear() Этот статический метод позволяет очистить указанный диапазон элементов и инициализировать их "безопасными" значениями. Массив целых получит нулевые значения элементов, строковый - пустые строки, ссылки преобретут значения null.
    CopyTo() Используется для копирования элементов из исходного массива в массив назначения.
    GetEnumerator() Возвращает интерфейс IEnumerator для указанного массива. Этот интерфейс необходим для применения конструкции цикла 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:
    ******************************************** 
      
    Исходный массив:
    Колян  Вован  Иван  Болван  Чурбан
      
    Инверсный массив:
    Чурбан  Болван  Иван  Вован  Колян
      
    Сортированный массив:
    Болван  Вован  Иван  Колян  Чурбан
      
    Частично очищенный массив:
    Болван        Чурбан

    Работа со строками в C#

    Встроенный тип string (строки Unicode ) относится к ссылочному типу данных и является псевдонимом класса System.String. Этот базовый класс обеспечивает программиста множеством полезных свойств и методов для обработки строк. Часть методов является методами объектов, часть - методами класса (статическими).

    Некоторые члены класса 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# строки могут содержать любое количество управляющих последовательностей ( escape characters - управляющих символов), на которые реагируют средства вывода строк.

    Управляющие последовательности
    Управляющий символ Назначение
    @ Префикс дословного вывода строк ( verbatim strings ). Ставится перед формируемой строкой и отменяет срабатывание всех управляющих символов
    \' Вставить одинарную кавычку
    \" Вставить двойную кавычку
    \\ Вставить обратный слэш
    {{ Вставить фигурную открывающую скобку
    }} Вставить фигурную закрывающую скобку
    \a Запустить системное звуковое оповещение ( Alert )
    \b Вернуться на одну позицию ( Backspace )
    \f Начать следующую страницу ( Form feed )
    \n Вставить новую строку ( New line )
    \r Вставить возврат каретки ( carriage Return )
    \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);
      }
    }
    }

    Результат выполнения примера

    Я передаю "Привет всем!" {Снетков}

    Применение класса System.Text.StringBuilder

    При работе со строками в C# первоначальное значение строки не может быть изменено. Мы применяем к строкам множество модифицирующих методов и получаем нужный результат, но на самом деле методы каждое изменение строки записывают на новом месте в куче, а не используют одну и ту же область памяти. Ссылки на старые места хранения строк безжалостно бросаются, а методы возвращают лишь копии измененного содержимого, размещенного на новом месте. Брошенные ссылки подберет позже сборщик мусора, когда будет отдавать системе уже неадресуемые места памяти.

    Когда потребуется не создавать лишние копии строк (особенно для больших объемов данных), а обрабатывать строки напрямую в месте их хранения, можно воспользоваться классом StringBuilder в пространстве имен System.Text.

    Создание объекта класса StringBuilder осуществляется при помощи одного из многих его перегруженных конструкторов. Параметры конструкторов позволяют задавать значения четырех свойств, приведенных в таблице.

    Свойства, задаваемые через параметры конструкторов StringBuilder при создании объекта
    Свойство Назначение
    Capacity (емкость) Свойство определяет исходный размер элемента символьного массива в экземпляре класса StringBuilder. По умолчанию принято значение 16. Если существующий размер меньше, чем необходимо для такой операции как добавление, то объект класса StringBuilder удваивает размер символьного массива. Поскольку такие операции снижают производительность, рекомендуется заранее задавать необходимый размер, если он известен на момент создания объекта класса StringBuilder.
    Length (длина) Длина строки в данном экземпляре объекта класса StringBuilder.
    MaxCapacity (максимальная емкость) Свойство задает максимальное количество символов, которое может содержать текущий экземпляр класса StringBuilder. По умолчанию принято значение Int32.MaxValue. Свойство StringBuilder.MaxCapacity уже созданного объекта не может быть изменено.
    Chars (символы) Это индексируемое свойство внутреннего символьного массива, входящего в объект StringBuilder. Индексы этого свойства начинаются с нуля.

    Методы класса 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();
      }
    }
    }

    Результат выполнения примера

    Эта исходная строка не меняется
    ЭТА ИСХОДНАЯ СТРОКА НЕ МЕНЯЕТСЯ
      
    Исходная строка+Добавленная строка

    Перечисления в C#

    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();
      }
    }
    }
    Вернуться к учебному плану