БлогNot. Примеры консольных приложений на C#

Примеры консольных приложений на C#

сейчас в списке: 16 приложений Здесь размещены небольшие учебные примеры приложений на C#, проверенные в актуальной на момент создания статьи сборке Visual Studio 2019.

Все программы в ветке - консольные, то есть, созданы командой Файл -> Создать -> Проект -> C# -> Консольное приложение (.NET Framework), после чего текст примера можно скопировать вместо сгенерированного содержимого файла Program.cs.

Логического порядка в примерах нет, будут добавляться по мере написания. Для поиска на странице нужного слова используйте комбинацию клавиш Ctrl+F в своём браузере.

1. Табулирование функций

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

//C# -> Консольное приложение (.NET Framework)
using System;

namespace ConsoleApp1 {
 class Program {
  delegate double Function (double x);
  static void Table (double x1, double dx, double x2, Function f) {
   double y;
   string OutputFormat = "{0:F4}	{1:F4}";
   Console.WriteLine (OutputFormat, "x", "y");
   for (double x = x1; x <= x2; x += dx) {
    y = f (x);
    Console.WriteLine (OutputFormat,x,y);
   }
  }
  static void Main (string[] args) {
   double sin_ (double x) { return Math.Sin (x); }
   double cos_ (double x) { return Math.Cos (x); }

   Console.Clear ();
   Function function = sin_;
   Table (0, Math.PI / 10, Math.PI, function);
   function = cos_;
   Table (0, Math.PI / 10, Math.PI, function);

   Console.ReadKey();
  }
 }
}
2. Второй класс в проекте, генерирующий случайное число

Для добавления нового класса достаточно обратиться к меню Проект -> Добавить класс и назначить новому классу имя.

Если классы располагаются в одном пространстве имён, для ссылки на метод объекта второго класса главной программе достаточно выполнить что-то вроде

var obj = new ProjectNamespace.SecondClass();
obj.Method();

//---------------------- файл Program.cs

//C# -> Консольное приложение (.NET Framework)
using System;

namespace ConsoleApp1 {
 class Program {
  static void Main (string[] args) {
   var gen = new ConsoleApp1.RandomNumber();
   Console.WriteLine ("{0:F6}",gen.get());
   Console.ReadKey();
  }
 }
}

//---------------------- файл RandomNumber.cs

//меню Проект - Добавить класс
using System;

namespace ConsoleApp1 {
 class RandomNumber {
  private double min, max;
  private Random rand;
  public RandomNumber (double min = 0, double max = 1) {
   this.rand = new Random ();
   this.min = min;
   this.max = max;
  }
  public double get () {
   return rand.NextDouble() * ( this.max - this.min ) + this.min;
  }
 }
}
3. Два класса в одном файле

Достаточно, чтобы каждый класс располагался в своих операторных скобках внутри общего namespace

//C# -> Консольное приложение (.NET Framework)
using System;

namespace ConsoleApp1 {
 class A {
  private string str;
  private double val;
  public A(string str="", double val = 0) { this.str = str;  this.val = val; }
  public string view() {
   return str + ", " + val.ToString(); 
  }
 }
 class Program {
  static void Main (string[] args) {
   string name = "Паоло Гудини";
   A a = new A(name,1);
   Console.WriteLine (a.view());
   Console.ReadKey();
  }
 }
}
4. Матрицы обычная и ступенчатая

По сути, в C# есть "паскалеподобная" матрица с квадратными скобками вида [i,j], для которой количество элементов в каждой строке одинаково, и "си-подобная" ("ступенчатая") со скобками вида [i][j] и возможностью рассматривать матрицу как вектор векторов, соответственно, имея в различных строках различное количество элементов.

Показаны выделение памяти, заполнение и построчный вывод элементов.

//C# -> Консольное приложение (.NET Framework)
using System;

namespace ConsoleApp1 {
 class Program {
  static void PrintMatrix(int [,] a) {
   for (int i = 0; i < a.GetLength(0); i++) {
    for (int j = 0; j < a.GetLength(1); j++) {
     Console.Write ("{0:F4} ",a[i,j]);
    }
    Console.WriteLine ();
   }
  }
  static void PrintJaggedMatrix (int [][] a) {
   for (int i = 0; i < a.Length; i++) {
    for (int j = 0; j < a[i].Length; j++) {
     Console.Write ("{0:F4} ", a[i][j]);
    }
    Console.WriteLine ();
   }
  }
  static void Main (string[] args) {

   //"Паскалеподобная" матрица, количество элементов в каждой строке одинаково
   int [,] matrix = {
    {1,2,3 },
    {4,5,6 }
   };
   PrintMatrix (matrix);

   //"Си-подобная" матрица, количество элементов в каждой строке может быть различным
   const int jaggedMarixRows = 3;
   int [] [] jaggedMarix = new int [jaggedMarixRows] [];
   for (int i = 0, k = 1; i < jaggedMarix.Length; i++) { 
    try {
     jaggedMarix [i] = new int [i + 1];
    }
    catch (OutOfMemoryException e) {
     Console.WriteLine ("Memory allocation failed \"{0:C}\" in string {0:D}\n",e,i);
     break;
    }
    for (int j = 0; j < jaggedMarix[i].Length; j++) jaggedMarix [i] [j] = k++;
   }
   PrintJaggedMatrix (jaggedMarix);

   Console.ReadKey ();
  }
 }
}
5. Шаблон класса стека

Показаны стек целых и стек вещественных чисел, использующие один и тот же шаблон класса.

//C# -> Консольное приложение (.NET Framework)
using System;

namespace ConsoleApp1 {

 public class Stack <T> where T : new() { 
                                  //у типа данных стека должен быть конструктор по умолчанию
  T [] stck; // массив, содержащий стек
  int tos; // индекс вершины стека
  public Stack (int size) {
   stck = new T [size]; // распределить память для стека
   tos = 0;
  }
  public int Push (T ch) { // Поместить элементы в стек
   if (tos == stck.Length) { //стек заполнен
    return -1;
   }
   stck [tos] = ch;
   tos++;
   return tos;
  }
  public T Pop () { // Извлечь элемент из стека
   if (tos == 0) { //стек пуст
    return default (T);
   }
   tos--;
   return stck [tos];
  }
  public bool IsFull () { // Возвратить значение true, если стек заполнен
   return tos == stck.Length;
  }
  public bool IsEmpty () { // Возвратить значение true, если стек пуст
   return tos == 0;
  }
  public int Capacity () { // Возвратить общую емкость стека
   return stck.Length;
  }
  public int GetNum () { // Возвратить количество объектов, находящихся в данный момент в стеке
   return tos;
  }
 }

 class Program {
  static void Main (string[] args) {
   const int Stack1Size = 10;
   Stack <int> stk1 = new Stack <int> (Stack1Size);
   for (int i = 0; !stk1.IsFull (); i++) stk1.Push (i+1);
   if (stk1.IsFull ()) Console.WriteLine ("Стек stk1 заполнен.");
   // Вывести содержимое стека stk1.
   Console.Write ("Содержимое стека stk1: ");
   while (!stk1.IsEmpty ()) {
    int i = stk1.Pop ();
    Console.Write ("{0} ", i);
   }
   Console.WriteLine ();
   if (stk1.IsEmpty ()) Console.WriteLine ("Стек stk1 пуст.\n");
   // Поместить дополнительные символы в стек stk1.
   Console.WriteLine ("Вновь поместить элементы в стек stk1.");
   for (int i = 0; !stk1.IsFull (); i++) stk1.Push (Stack1Size - i );
   Console.WriteLine ("А теперь извлечь символы из стека stk1 " +
    "и поместить их в стек stk2, добавив дробную часть");
   Stack <double> stk2 = new Stack <double> (Stack1Size);
   while (!stk1.IsEmpty ()) {
    int i = stk1.Pop ();
    stk2.Push ((double)i + 0.5);
   }
   Console.Write ("Содержимое стека stk2: ");
   while (!stk2.IsEmpty ()) {
    double d = stk2.Pop ();
    Console.Write ("{0:F1} ", d);
   }
   Console.WriteLine ();
   Console.WriteLine ("Емкость стека stk2: " + stk2.Capacity ());
   Console.WriteLine ("Количество объектов в стеке stk2: " + stk2.GetNum ());

   Console.ReadKey ();
  }
 }
}
6. Шаблон функции и аргументы по ссылке

Передача и возврат аргументов по ссылке, функция с переменным числом аргументов.

//C# -> Консольное приложение (.NET Framework)
using System;

namespace ConsoleApp1 {

 class Program {
  static void Swap <T> (ref T lhs, ref T rhs) { //Аргументы получены по ссылке
   T temp = lhs;   lhs = rhs;   rhs = temp;
  }

  static void GetNumberParts (double n, out double whole, out double frac) {
   //Второй и третий аргументы будут возвращены по ссылке
   whole = Math.Floor(n);
   frac = n - whole;
  }

  static double Summa (params double [] nums) { //Функция может иметь переменное число аргументов
   double sum = 0;
   for (int i = 0; i < nums.Length; i++) sum += nums [i];
   return sum;
  }

  static void Main (string[] args) {
   
   double a = 1.5;
   double b = 2.7;
   Swap (ref a, ref b); //Аргументы переданы по ссылке
   Console.WriteLine (a + " " + b);

   double d, f;
   GetNumberParts (a, out d, out f); //Второй и третий аргументы будут возвращены по ссылке
   Console.WriteLine ("Целая часть числа равна " + d);
   Console.WriteLine ("Дробная часть числа равна " + f);

   double s = Summa (1, 2, 3, 4);
   Console.WriteLine ("1+2+3+4= " + s);

   Console.ReadKey ();
  }
 }
}
7. Фабрика объектов

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

//C# -> Консольное приложение (.NET Framework)
using System;

namespace ConsoleApp1 {
 class Factory {
  private int val;
  private Factory (int val = 0) { //Конструктор класса приватен
   this.val = ++val;
  }
  public static Factory makeFactory (int val = 0) { //Но есть фабрика объектов
   Factory factory = new Factory (val);
   return factory;
  }
  public override string ToString () { //переписываем встроенный метод
   return val.ToString ();
  }
 }

 class Program {
  static void Main (string[] args) {
   const int Size = 10;
   Factory [] Objects = new Factory [Size];
   //Объекты массива Factory пока пустые ссылки (null), то есть, конструктор по умолчанию
   //всё равно доступен. А вот new Factory (0) [Size] не сработало бы
   for (int i = 0; i < Size; i++) {
    Objects [i] = Factory.makeFactory (i);
    Console.WriteLine ("Объект номер {0}: {1}",i, Objects [i]);
   }
   Console.ReadKey ();
  }
 }
}
8. Статические члены класса и оценивание арифметических выражений

Описываем в классе статический счётчик созданных объектов и оцениваем арифметические выражения одной строчкой кода с проверкой корректности (метод Exec).

//C# -> Консольное приложение (.NET Framework)
using System;
using System.Data;

namespace ConsoleApp1 {
 class Program {

  class Compute {
   private static int Count = 0; //Счётчик созданных объектов класса
   public Compute () {
    Count++; //Увеличить счётчик на 1 при создании объекта
   }
   public double Exec (string expr) {
    return Convert.ToDouble (new DataTable ().Compute (expr, ""));
   }
   public static int GetCount () { return Count; } //Узнать значение счётчика
  }
  static void Main () {
   string [] Expressions = {
    "(5-2)%2 + 5./4",
    "-1+2/3",
    "1*2*3*error"
   };
   for (int i = 0; i < Expressions.Length; i++) {
    Compute Expr = new Compute (); //На самом деле, хватило бы статического метода в Compute
    try {
     double d = Expr.Exec (Expressions [i]);
     Console.WriteLine ("{0} = {1:F4}", Expressions [i], d);
    }
    catch (Exception e) {
     Console.WriteLine ("Ошибочное выражение {0}: \"{1}\"", Expressions [i], e.Message);
    }
   }

   Console.WriteLine ("Всего обработано выражений: {0}", Compute.GetCount ());
   Console.ReadKey ();
  }
 }
}
9. Работаем с объектом "Таблица данных"

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

//C# -> Консольное приложение (.NET Framework)
using System;
using System.Data;

namespace ConsoleApp1 {

 class MyDataTable {
  DataTable dt;
  DataRow dr;
  DataColumn dc;
  public MyDataTable (string TableName, string [] ColumnNames, Type [] ColumnTypes) {
   dt = new DataTable ();
   dt.TableName = TableName;
   for (int i = 0; i < ColumnNames.Length; i++) dt.Columns.Add (ColumnNames [i], ColumnTypes [i]);
  }
  public DataRow CreateRow () { //Создать строку
   dr = dt.NewRow (); 
   return dr;
  }
  public void Add (DataRow dr) { //Добавить строку в базу
   dt.Rows.Add (dr); 
  }
  public DataColumn CreateColumn (string Name, Type type, string expr) { //Создать вычисляемый столбец
   dc = new DataColumn ();
   dc.ColumnName = Name;
   dc.DataType = type;
   dc.Expression = expr;
   dt.Columns.Add (dc);
   return dc;
  }
  public void AddRow (DataRow dr) { //Добавить строку в базу
   dt.Rows.Add (dr);
  }

  public string Exec (string expr) {
   return Convert.ToString (dt.Compute (expr, ""));
  }
 }

 class Program {
  static void Main () {
   string [] ColumnNames = { "Имя", "Оплата", "Комиссия"};
   Type [] ColumnTypes = { typeof (string), typeof (int), typeof (double) };
   MyDataTable Formula = new MyDataTable ("Таблица", ColumnNames, ColumnTypes);
    //Создали таблицу
   DataRow dr = Formula.CreateRow ();
   dr ["Имя"] = "Вася";
   dr ["Оплата"] = 30000;
   dr ["Комиссия"] = 0.15;
   Formula.AddRow (dr);
   dr = Formula.CreateRow ();
   dr ["Имя"] = "Петя";
   dr ["Оплата"] = 40000;
   dr ["Комиссия"] = 0.20;
   Formula.AddRow (dr);
    //Добавили данные в строки
   Console.WriteLine ("Avg(Оплата) = " + Formula.Exec ("Avg(Оплата)"));
    //Посчитали по формуле
   DataColumn dc = Formula.CreateColumn ("Итого", typeof (double), "Оплата - Оплата * Комиссия");
   Console.WriteLine ("Sum(Итого) = " + Formula.Exec ("Sum(Итого)"));
    //Посчитали выражение над добавленным столбцом
    //(30000-30000*0.15)+(40000-40000*0.20)

   Console.ReadKey ();
  }
 }
}
10. Запускаем десять потоков и выводим их состояние

Управляем временем выполнения потоков, а также позицией курсора в консоли (для вывода сообщений о состоянии потоков).

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1 {
 internal class Program {
  private static void Main (string [] args) {
   
   var Tasks = new List<Task> ();
   for (var i = 0; i < 10; i++) Tasks.Add (new Task (Method, TaskCreationOptions.LongRunning));

   Tasks.ForEach (t => t.Start ());

   var startY = Console.CursorTop;

   do {
    PrintStatus (Tasks);
    Console.CursorTop = startY;
   } while (!Task.WaitAll (Tasks.ToArray (), TimeSpan.FromSeconds (1)));

   PrintStatus (Tasks);

   Console.Write ("Выполнено");
   Console.ReadKey ();
  }

  private static void PrintStatus (IEnumerable <Task> Tasks) {
   foreach (var task in Tasks)
    Console.WriteLine ($"Состояние задачи #{task.Id}: {task.Status}");
  }

  private static void Method () {
   Thread.Sleep (TimeSpan.FromSeconds (2 * Task.CurrentId ?? 1));
  }
 }
}
11. Перегрузка операторов

Показаны все основные виды переопределения операторов в классе C# (кроме некоторых редко используемых нюансов).

using System;

namespace ConsoleApp1 {
 class Point3D {
  private double x, y, z; //Трёхмерные координаты
  public Point3D () { x = y = z = 0; }
  public Point3D (double x, double y, double z = 0) { this.x = x; this.y = y; this.z = z; }
  public static Point3D operator + (Point3D op1, Point3D op2) { //Перегрузить бинарный оператор +
   Point3D result = new Point3D ();
   result.x = op1.x + op2.x;
   result.y = op1.y + op2.y;
   result.z = op1.z + op2.z;
   return result;
  }
  public static Point3D operator + (Point3D op1, double op2) { 
   //Перегрузить бинарный + для сложения с числом (вторым операндом)
   Point3D result = new Point3D ();
   result.x = op1.x + op2;
   result.y = op1.y + op2;
   result.z = op1.z + op2;
   return result;
  }
  public static Point3D operator + (double op1, Point3D op2) {
   //Перегрузить бинарный + для сложения с числом, если число является первым операндом
   Point3D result = new Point3D ();
   result.x = op2.x + op1;
   result.y = op2.y + op1;
   result.z = op2.z + op1;
   return result;
  }
  public static Point3D operator - (Point3D op) { // Перегрузить унарный оператор -
   Point3D result = new Point3D ();
   result.x = -op.x;
   result.y = -op.y;
   result.z = -op.z;
   return result;
  }
  public static bool operator < (Point3D op1, Point3D op2) { // Оператор сравнения
   return (
    Math.Sqrt (op1.x * op1.x + op1.y * op1.y + op1.z * op1.z) <
    Math.Sqrt (op2.x * op2.x + op2.y * op2.y + op2.z * op2.z) ? true : false );
  }
  public static bool operator > (Point3D op1, Point3D op2) { // "Меньше" и "больше" работают только вместе
   return (
    Math.Sqrt (op1.x * op1.x + op1.y * op1.y + op1.z * op1.z) >
    Math.Sqrt (op2.x * op2.x + op2.y * op2.y + op2.z * op2.z) ? true : false );
  }
  public static bool operator true (Point3D op) { 
   // Перегрузка true, истинна, если хотя бы 1 координата не равна 0
   return op.x != 0 || op.y != 0 || op.z != 0 ? true : false;
  }
  public static bool operator false (Point3D op) { // true и false работают только вместе
   return op.x == 0 && op.y == 0 && op.z == 0 ? true : false; //все координаты равны 0
  }
  public static bool operator | (Point3D op1, Point3D op2) { 
   // Перегрузка логического "или", истинна, если хотя бы одна коррдината ненулевая
   return op1.x * op2.x != 0 || op1.y * op2.y != 0 || op1.z * op2.z != 0 ? true : false;
  }
  public static bool operator & (Point3D op1, Point3D op2) {
   // Перегрузка логического "и", истинна, если все коррдинаты ненулевые
   return op1.x * op2.x != 0 & op1.y * op2.y != 0 & op1.z * op2.z != 0 ? true : false;
  }
  public static bool operator ! (Point3D op) {
   // Перегрузка true, ложна, если хотя бы 1 координата не равна 0
   return op.x != 0 || op.y != 0 || op.z != 0 ? false : true;
  }
  public static implicit operator double (Point3D op1) {
   //Неявное преобразование типа, выполняется автоматически, когда объект используется
   //в выражении вместе со значением целевого типа
   return op1.x * op1.y * op1.z;
  }
  public static explicit operator float (Point3D op1) {
   //Явное преобразование типа
   return (float)op1.x * (float) op1.y * (float) op1.z;
  }

  public override string ToString () {
   // Вернуть координаты в виде строки, перегрузив метод ToString по умолчанию
   return this.x + ", " + this.y + ", " + this.z;
  }
}
internal class Program {
  private static void Main () {
   Point3D a = new Point3D (1, 2, 3);
   Point3D b = new Point3D (1, 1, 1);
   Point3D c = new Point3D ();
   Console.WriteLine ("Координаты точки a: {0}", a.ToString ());
   Console.WriteLine ("Координаты точки b: {0}", b.ToString ());
   Console.WriteLine ("Координаты точки c: {0}", c.ToString ());
   Point3D d = a + b + c; 
   Console.WriteLine ("A + B + C = {0}", d.ToString ());

   d = -a;
   Console.WriteLine ("D = -A = {0}", d.ToString ());
   d += 1; //не нужно отдельно перегружать оператор "+="!
   Console.WriteLine ("D = -A + 1 = {0}", d.ToString ());
   d = 1 + d; //а здесь вызовется второй оператор для сложения с числом
   Console.WriteLine ("D = 1 + D = {0}", d.ToString ());

   bool cond = a < b;
   Console.WriteLine ("A < B = {0}", cond);
   cond = a > b;
   Console.WriteLine ("A > B = {0}", cond);

   if (c) Console.WriteLine ("Точка C истинна");
   else Console.WriteLine ("Точка C ложна");
   if (d) Console.WriteLine ("Точка D истинна");
   else Console.WriteLine ("Точка D ложна");

   if (a & d) Console.WriteLine ("a & d истинно");
   else Console.WriteLine ("a & d ложно");
   if (a | d) Console.WriteLine ("a | d истинно");
   else Console.WriteLine ("a | d ложно");
    //с "укороченными" формами &&, || такие перегрузки работать не будут
   if (!c) Console.WriteLine ("Точка !C истинна");
   else Console.WriteLine ("Точка !C ложна");

   double val  = d * 2 + b; // преобразовать в тип double неявно
   Console.WriteLine ("d * 2 + b = " + val);
   float fval = (float)b * (float)Math.PI; // преобразовать в тип float явно
   Console.WriteLine ("b * PI = " + fval);

   Console.ReadKey ();
  }

 }
}
12. Три способа преобразовать строку в число

Основные способы и простейшая обработка исключений при преобразовании.

using System;
using System.Globalization;

namespace ConsoleApp1 {
internal class Program {
  private static void Main () {
   
   //Способ 1: Parse 
   Int32 val1 = Int32.Parse ("100"); //100
    //простое преобразование
   Int32 val2 = Int32.Parse ("(200)", NumberStyles.AllowParentheses); //-200
    //перегрузка с указанием стиля
   int val3 = int.Parse ("30,000", NumberStyles.AllowThousands, new CultureInfo ("en-au")); //30000
    //перегрузка с указанием стиля и культуры
   Console.WriteLine ($"{val1} {val2} {val3}");

   //Способ 2: Convert
   val1 = Convert.ToInt32 ("123456"); //123456
   val2 = Convert.ToInt32 ("10000",2); //16
   val3 = Convert.ToInt32 ("100", 16); //256
   Console.WriteLine ($"{val1} {val2} {val3}");

   //Способ 3: TryParse 
   string numberStr = "123456";
   int number;
   bool isParsable = Int32.TryParse (numberStr, out number); //123456
   if (isParsable) Console.WriteLine (number);
   else Console.WriteLine ("Неверный формат: "+ numberStr);
   numberStr = "123,45";
   double val;
   isParsable = double.TryParse (numberStr, NumberStyles.Float, NumberFormatInfo.CurrentInfo, out val);
   //val будет равно 123,45 , если локаль русская
   if (isParsable) Console.WriteLine (val);
   else Console.WriteLine ("Неверный формат: "+ numberStr);

   //Обработка исключений при преобразованиях 1 и 2:
   string str1 = "100f";
   try {
    val1 = Int32.Parse (str1);
    Console.WriteLine (val1);
   }
   catch (Exception e) {
    Console.WriteLine ("Неверный формат Parse: " + str1);
   }
   try {
    val1 = Convert.ToInt32 (str1);
    Console.WriteLine (val1);
   }
   catch (Exception e) {
    Console.WriteLine ("Неверный формат ToInt32: " + str1);
   }
   
   Console.ReadKey ();
  }

 }
}
13. Шесть способов преобразовать число в строку

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

using System;
using System.Text;

namespace ConsoleApp1 {
 class Program {
  private static void Main () {

   //Способ 1: ToString
   int val = 0xff;
   Console.WriteLine (val.ToString ()); //255
   val = (int) 1e5;
   Console.WriteLine (val.ToString ()); //100000

   //Способ 2: "+" со строкой
   val = (int) Math.Floor(Math.PI);
   string str = "" + val;
   Console.WriteLine (str); //3

   //Способ 3: StringBuilder
   var builder = new StringBuilder (); //System.Text
   builder.Append ("There are ");
   builder.Append (val).ToString ();
   builder.Append (" wolfs in our forest");
   Console.WriteLine (builder); //There are 3 wolfs in our forest

   //Способ 4: Convert
   string msg = "There are " + Convert.ToString (val) + " wolfs in our forest";
   Console.WriteLine (msg); //There are 3 wolfs in our forest

   //Способ 5: Format
   string msg2 = string.Format ("There are {0} wolfs in our forest", val);
   Console.WriteLine (msg2); //There are 3 wolfs in our forest

   //Способ 6: $
   string msg3 = $"There are {val} wolfs in our forest";
   Console.WriteLine (msg3); //There are 3 wolfs in our forest

   Console.ReadKey ();
  }

 }
}
14. Индексаторы, свойства и автоматически реализуемые свойства

Пример на индексаторы и свойства C#.

Ограничения индексаторов таковы: значение, отдаваемое индексатором, нельзя передавать методу в качестве параметра ref или out, поскольку в индексаторе не определено место в памяти для его хранения. Индексатор должен быть членом своего класса и поэтому не может быть объявлен как static.

Cвойство сочетает в себе поле с методами доступа к нему и состоит из имени и аксессоров get и set. Аксессоры служат для получения и установки значения переменной. Имя свойства может быть использовано в выражениях и операторах присваивания аналогично имени обычной переменной, но в действительности при обращении к свойству по имени автоматически вызываются его аксессоры get и set.

Свойства не определяют место в памяти для хранения полей, а лишь управляют доступом к полям. Это означает, что само свойство не предоставляет поле, поэтому поле должно быть определено независимо от свойства. Свойство также не должно изменять состояние базовой переменной при вызове аксессора get. Исключение из этого правила составляет автоматически реализуемое свойство.

Автоматически реализуемое свойство не может быть доступно только для чтения или только для записи. При его объявлении нужно указывать оба аксессора — get и set, хотя любой из них можно сделать приватным, доступным только методам своего класса.

//C# -> Консольное приложение (.NET Framework)
using System;

namespace ConsoleApp1 {
 public class Array <T> where T : new() { //"Отказоустойчивый" вектор с индексаторами
  T [] a; //Ссылка на базовый массив
  public int Length { get; set; } //Длина массива сделана автосвойством
  public bool ErrFlag { get; private set; }
   //Результат последней операции - автосвойство "только для чтения" извне
  public Array (int size) { //Конструктор
   if (size > 0) {
    a = new T [size];
    Length = size;
   }
   else Length = 0;
  }
  
  public T this [int index] { //Индексатор
   get { //Аксессор get
    if (IndexIsValid (index)) {
     ErrFlag = false;
     return a [index];
    }
    else {
     ErrFlag = true;
     return default (T);
    }
   }
   set { //Аксессор set; получает неявный параметр value!
    if (IndexIsValid (index)) {
     a [index] = value;
     ErrFlag = false;
    }
    else ErrFlag = true;
   }
  }
  public T this [double index] { //Ещё один индексатор, для индекса типа double
   get {
    int intIndex = (int) Math.Round (index);
    return this [intIndex];
   }
   set {
    int intIndex = (int) Math.Round (index);
    this [intIndex] = value;
   }
  }
  public bool this [bool index] { 
   //Индексатор без set, только для чтения, возвращает состояние this.ErrFlag
   get {
    return ErrFlag;
   }
   //Аксессор set отсутствует
  }
  private bool IndexIsValid (int index) {
   if (Length < 1) return false;
   return (index >= 0 & index < Length ? true : false);
  }
 }

 public class Array2D <T> where T : new() { //"Отказоустойчивая" матрица с индексаторами
  T [,] a; //Ссылка на базовый массив
  int rows, cols; //Количество строк и столбцов, приватные
  int len; //Длина массива, на этот раз приватная
  public int Length { //Получим её свойством "только для чтения"
   get { return len; }
  }
  bool err; //Результат последней операции, приватный
  public bool ErrFlag { //Получим его свойством "только для чтения"
   get { return err; }
  }
  public Array2D (int r, int c) {
   if (r > 0 & c > 0) {
    rows = r; cols = c;
    a = new T [rows, cols];
    len = rows * cols;
   }
   else len = 0;
  }
  public void Print (String hdr = "") { //Вывод в консоль
   Console.WriteLine ();
   Console.WriteLine (hdr);
   for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++)
     Console.Write ("{0} ", a [i, j]);
    Console.WriteLine ();
   }
  }
  public T this [int row, int col] { //Двумерный индексатор
   get {
    if (IndexIsValid (row, col)) {
     err = false;
     return a [row, col];
    }
    else {
     err = true;
     return default (T);
    }
   }
   set {
    if (IndexIsValid (row, col)) {
     a [row, col] = value;
     err = false;
    }
    else err = true;
   }
  }
  public int Rows { //свойство Rows для изменения приватного количества строк
   get { return rows; }
   set { if (value > -1 & value < rows) rows = value; }
  }
  public int Cols { //свойство Rows для изменения приватного количества столбцов
   get { return cols; }
   set { if (value > -1 & value < cols) cols = value; }
  }
  private bool IndexIsValid (int r, int c) {
   if (len < 1) return false;
   return ( r > -1 & r < rows & c > -1 & c < cols ? true : false );
  }
 }

 class Program {
  static void Main (string [] args) {
   Array <int> arr = new Array <int> (5);
   for (int i = 0; i < arr.Length + 1; i++) { //Берём 1 лишний элемент!
    arr [i] = i + 1;
    Console.WriteLine (arr [i] + "  (ErrFlag = "+ arr.ErrFlag + ")");
   }
   Array <double> darr = new Array <double> (5);
   for (double x = 0.1; x < arr.Length ; x += 0.9) { //Берём 1 лишний элемент, 2-й индексатор
    darr [x] = x + 1;
    Console.WriteLine (arr [x] + "  (ErrFlag = " + arr.ErrFlag + ")");
   }
   Console.WriteLine ("arr [true]   = " + arr [true]); //Третий индексатор

   Array2D <int> matr = new Array2D<int> (2, 2);
   for (int i = 0; i < 3; i++) { //Лишняя строка
    Console.WriteLine();
    for (int j = 0; j < 2; j++) {
     matr [i, j] = i + j;
     Console.Write ("{0:D} ({1}) ", matr [i, j], matr.ErrFlag);
    }
   }
   matr.Rows = 2; //Меняем количество строк с помощью свойства
   matr.Print ("Матрица после изменения количества строк");

  Console.ReadKey ();
  }
 }

}
15. Наследование

Довольно развёрнутый пример, показывающий особенности реализации наследования в классах C#.

Абстрактный, базовый и производный классы, приватные данные и публичные свойства-"обёртки" над ними, абстрактные и виртуальные методы, приватные, публичные и защищённые члены класса, неуниверсальный статический класс с расширениями, класс, запрещённый к наследованию, массив из объектов базового класса.

//C# -> Консольное приложение (.NET Framework)
using System;
using System.Collections.Generic;

namespace ConsoleApp1 {
 abstract class Shape { //Абстрактный класс "фигура"
  public abstract string GetTypeString (); //Абстрактный метод, должен быть определён у потомков
 }

 class Shape2D : Shape { //Базовый класс - рамка фигуры
  double width, height; //Приватные ширина и высота
  int frac = 2; //Количество знаков в дробной части при выводе
  public Shape2D (double width = 0, double height = 0, int frac = 2) {
   //Конструктор базового класса-"рамки"
   Width = width;
   Height = height;
   this.frac = frac;
  }
  public double Width { // Публичные свойства ширины и высоты двумерного объекта
   get { return width; }
   set { width = value < 0 ? -value : value; }
  }
  public double Height {
   get { return height; }
   set { height = value < 0 ? -value : value; }
  }
  public int Frac { //Публичное свойство "количество знаков в дробной части"
   get { return frac; }
   set { width = frac < 1 ? 0 : ( frac > 14 ? 14 : value ); }
  }
  public double RoundTo (double d) { //Округлить до нужного количества знаков
   return Math.Round (d, Frac);
  }
  public string GetSizeString () { //Строка с габаритами
   return RoundTo (Width) + " x " + RoundTo (Height);
  }
  public override string GetTypeString () { //Реализация абстрактного метода
   return "рамка фигуры";
  }
  public virtual double Area () { //Виртуальный метод "площадь рамки"
   return RoundTo (Width * Height);
  }
 }

 class Triangle : Shape2D { //Производный класс - треугольник
  protected string Style; //Тип треугольника, защищённое свойство
  protected double A, B, C; //Стороны треугольника, защищённые свойства
  public Triangle (double A = 3, double B = 4, double C = 5) {
   if (A + B > C & A + C > B & B + C > A) {
    double [] Temp = new double [] { A , B , C };
    Array.Sort (Temp);
    this.C = Width = Temp [2];
    this.B = Temp [1];
    this.A = Temp [0];
    Height = GetHeight (Temp [1]);
    var Temp2 = new List <double> () { Temp [0] * Temp [0], Temp [1] * Temp [1], Temp [2] * Temp [2] };
    this.Style = Temp2 [2] < Temp2 [0] + Temp2 [1] ?
     (Temp2 [0] == Temp2 [1] ? "равносторонний" : "остроугольный") :
     (Temp2 [2] > Temp2 [0] + Temp2 [1] ? "тупоугольный" : "прямоугольный");
   }
   else {
    this.A = this.B = this.C = Width = Height = 0;
    this.Style = "не существует";
   }
  }
  private double GetHeight (double a) {//высота, опущенная на сторону a (большую из всех)
   double P = ( A + B + C ) / 2;
   return RoundTo (2 * Math.Sqrt (P * ( P - A ) * ( P - B ) * ( P - C )) / A);
  }
  public override double Area () { //Площадь треугольника
   double P = ( A + B + C ) / 2;
   return RoundTo (Math.Sqrt(P * (P - A) * (P - B) * (P - C)));
  }
  public override string GetTypeString () { //Переопределение виртуального метода
   return Style;
  }
 }
 public static class Measures { //Неуниверсальный статический класс с расширениями
  public static double ToRadians (this double angleInDegree) { //Угол в градусах - в радианы
   return ( angleInDegree * Math.PI ) / 180.0;
  }
 }

 sealed class Triangle2С : Triangle {
  //Производный от треугольника - треугольник, заданный двумя сторонами и углом между ними
  //Этот класс наследовать уже нельзя (sealed)
  public Triangle2С (double A, double B, double angleC) : 
   base (A, B, 
    Math.Sqrt(Math.Pow(A,2) + Math.Pow(B,2) - 2*A*B*Math.Cos(Measures.ToRadians(angleC)))) {
   //использует конструктор базового класса, вычислив по теореме косинусов третью сторону
  }
 }

 class Program { //Главный класс, демонстрация
  static void Main (string [] args) {
   Triangle t1 = new Triangle ();
   Console.WriteLine ("Сведения об объекте t1: тип {0}, габариты {1}", 
    t1.GetTypeString (), t1.GetSizeString ());
   Console.WriteLine ("Площадь равна " + t1.Area () + System.Environment.NewLine);

   Triangle t2 = new Triangle (4,4,4);
   Console.WriteLine ("Сведения об объекте t2: тип {0}", t2.GetTypeString ());
   Console.WriteLine ("Габариты равны {0}x{1}", t2.Width, t2.Height);
   Console.WriteLine ("Площадь равна " + t2.Area ());
   Console.WriteLine ();

   Triangle2С t3 = new Triangle2С (4,4,60); //Совпадёт со 2-м, но задан по-другому
   Console.WriteLine ("Сведения об объекте t3: тип {0}, габариты {1}",
    t3.GetTypeString (), t3.GetSizeString ());
   //тип изменился с "равносторонний" на "остроугольный" из-за погрешностей при расчёте C!
   Console.WriteLine ("Площадь равна " + t3.Area ());
   Console.WriteLine ();

   Shape2D [] Shapes = new Shape2D [4]; //Массив объектов базового класса
   Shapes [0] = t1;
   Shapes [1] = t2;
   Shapes [2] = t3;
   Shapes [3] = new Shape2D(3,-4,1); //Свойство Height превратит "-4" в "4"
   foreach (var Shape in Shapes)
    Console.WriteLine ($"Тип объекта = {Shape.GetTypeString()}");
   Console.WriteLine ();

   Console.ReadKey ();
  }
 }

} //namespace

Далее показан вывод этого приложения:

Сведения об объекте t1: тип прямоугольный, габариты 5 x 4
Площадь равна 6

Сведения об объекте t2: тип равносторонний
Габариты равны 4x3,46
Площадь равна 6,93

Сведения об объекте t3: тип остроугольный, габариты 4 x 3,46
Площадь равна 6,93

Тип объекта = прямоугольный
Тип объекта = равносторонний
Тип объекта = остроугольный
Тип объекта = рамка фигуры
16. Упаковка и распаковка

Все типы в С#, включая простые типы значений, являются производными от класса object. Следовательно, ссылкой типа object можно воспользоваться для обращения к любому другому типу, в том числе и к типам значений.

Когда ссылка на объект класса object используется для обращения к типу значения, такой процесс называется упаковкой. Упаковка приводит к тому, что значение простого типа сохраняется в экземпляре объекта, т.е. "упаковывается" в объекте, который затем используется как и любой другой объект. Но в любом случае упаковка происходит автоматически. Для этого достаточно присвоить значение переменной ссылочного типа object, а об остальном позаботится компилятор.

Распаковка представляет собой процесс извлечения упакованного значения из объекта. Это делается с помощью явного приведения типа ссылки на объект класса object к соответствующему типу значения. Попытка распаковать объект в другой тип может привести к ошибке времени выполнения.

В примере значение типа int передаётся в качестве аргумента методу Sqr(), который, в свою очередь, принимает параметр типа object.

Также показана работа с массивом из разнотипных элементов, точнее, из элементов базового класса object.

//C# -> Консольное приложение (.NET Framework)
using System;

namespace ConsoleApp1 {

 class BoxingDemo {
  int x;
  BoxingDemo (int x = 0) { this.x = x;  } //Конструктор
  public override string ToString () { return x.ToString (); }
  //Переопределение метода ToString класса object
  public static int Sqr (object о) { //Метод для возведения в квадрат с параметром типа object
   return (int) о * (int) о;
  }
 }

 class Program { //Главный класс Program
  static int Main () {
   int x = 10;
   Console.WriteLine ("x = " + x);
   x = BoxingDemo.Sqr (x);
    // значение x автоматически упаковывается, когда оно передается методу Sqr()
   Console.WriteLine ("x^2 = " + x);

   object obj = x; // упаковать значение переменной х в объект
   int y = (int) obj; 
    // распаковать значение из объекта, доступного по ссылке obj, в переменную типа int
   Console.WriteLine ("y = " + y);

   object [] arr = new object [3]; //массив из разнотипных значений
   arr [0] = x;
   arr [1] = (double) x + 0.5;
   arr [2] = "Привет";
   for (int i = 0; i < arr.Length; i++) {
    var item = arr [i];
    var type = item.GetType ();
    Console.WriteLine ($"arr[{i}] = {item} ({type})");
   }
    
   Console.ReadKey ();
   return 0;
  }
 } //Program

} //namespace

теги: программирование список учебное c#

09.01.2021, 18:29; рейтинг: 57