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

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

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

Все программы в ветке - консольные, то есть, созданы командой Файл -> Создать -> Проект -> 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
17. Табулирование функции

В отличие от примера 1, не применяем делегатов, но проверяем допустимость входных данных и не выпускаем пользователя из цикла ввода, пока он не введёт корректные данные. Функция для примера такая:

функция для табулирования на C#
функция для табулирования на C#
using System;

namespace ConsoleApp1
{
 class Program
 {
  static void Main()
  {
   double x1, dx, x2, x, y;
   //Хитрый ввод
   Console.Clear();
   while (true)
   {
    Console.Write("Хотите ввести данные (y/n)? ");
    ConsoleKeyInfo key = Console.ReadKey();
    if (key.Key == ConsoleKey.N)
    {
     x1 = -5.0; dx = 0.05; x2 = 5.0; //поставить значения по умолчанию
     break;
    }
    else if (key.Key == ConsoleKey.Y)
    {
     try
     {
      Console.WriteLine();
      Console.Write("X1=");
      x1 = Convert.ToDouble(Console.ReadLine());
      Console.Write("dX=");
      dx = Convert.ToDouble(Console.ReadLine());
      Console.Write("X2=");
      x2 = Convert.ToDouble(Console.ReadLine());
     }
     catch (System.FormatException ex)
     {
      Console.WriteLine(ex.Message);
      continue;
     }
     if (!(Math.Abs(dx) > 1e-4 &
        (dx > 0 & x1 + dx <= x2 || dx < 0 & x1 + dx >= x2))) {
      //Дополнительные проверки корректности данных -
      //шаг не слишком мал и есть хотя бы 2 строки таблицы.
      //Сверху количество шагов не ограничиваем
      Console.WriteLine
       ("Введите допустимые начальное значение, шаг, конечное значение");
      continue;
     }
     break;
    }
   }
   //Обработка и форматный вывод
   string OutputFormat = "{0,10:F4}\t{1,10:F4}";
   Console.Clear();
   Console.WriteLine(OutputFormat, "x", "y");
   if (x1 > x2)
   {
    double t = x1; x1 = x2; x2 = t; dx = -dx;
   }
   for (x = x1; x <= x2; x += dx)
   {
    if (Math.Abs(x) < 1.0E-12) y = Double.NaN;
    //учёл разрыв - на самом деле, формально его в моём варианте нет
    else if (x < 0) y = Math.Log(Math.Abs(x));
    else y = Math.Sqrt(x);
    Console.WriteLine(OutputFormat, x, y);
   }
   Console.ReadKey();
  }
 }
}
18. Простейший консольный калькулятор на C#

На 4 арифметических действия, с базовыми проверками корректности и возможностью вычислить несколько выражений.

using System;

namespace Calculator
{
 class Calculator
 {
  public static double DoOperation(double num1, double num2, string op)
  {
   double result = double.NaN; //Ставим результату по умолчанию значение "не число"
   //Смотрим действие и выполняем
   switch (op)
   {
    case "+":
     result = num1 + num2;
     break;
    case "-":
     result = num1 - num2;
     break;
    case "*":
     result = num1 * num2;
     break;
    case "/":
     if (num2 != 0)
     {
      result = num1 / num2;
     }
     break;
    default: break;
   }
   return result;
  }
 }
 class Program
 {
  static void Main(string[] args)
  {
   bool endApp = false;
   //Выводим слова
   Console.WriteLine("Консольный калькулятор на C#\n");
   do
   {
    string numInput1 = "";
    string numInput2 = "";
    double result = 0;
    Console.Write("Введите первый аргумент и нажмите Enter: ");
    numInput1 = Console.ReadLine();
    double cleanNum1 = 0;
    while (!double.TryParse(numInput1, out cleanNum1))
    {
     Console.Write("Это неверный ввод. Пожалуйста, введите допустимое число: ");
     numInput1 = Console.ReadLine();
    }
    Console.WriteLine("Введите действие из списка:");
    Console.WriteLine("\t+ - Сложить");
    Console.WriteLine("\t- - Вычесть");
    Console.WriteLine("\t* - Умножить");
    Console.WriteLine("\t/ - Разделить");
    Console.Write("Ваш выбор? ");
    string op = Console.ReadLine().Substring(0, 1);
    string ops = "+-*/";
    while (ops.IndexOf(op) == -1)
    {
     Console.Write("Это неверный ввод. Пожалуйста, выберите допустимое действие: ");
     op = Console.ReadLine();
     if (op.Length > 1) op = op.Substring(0, 1);
    }
    Console.Write("Введите второй аргумент и нажмите Enter: ");
    numInput2 = Console.ReadLine();
    double cleanNum2 = 0;
    while (!double.TryParse(numInput2, out cleanNum2))
    {
     Console.Write("Это неверный ввод. Пожалуйста, введите допустимое число: ");
     numInput2 = Console.ReadLine();
    }
    try
    {
     result = Calculator.DoOperation(cleanNum1, cleanNum2, op);
     if (double.IsNaN(result) | double.IsInfinity(result))
     {
      Console.WriteLine("Операция не может быть выполнена\n");
     }
     else Console.WriteLine("Ваш результат: {0:0.##}\n", result);
    }
    catch (Exception e)
    {
     Console.WriteLine("Ошибка при использовании Math.\nДетали: " + e.Message);
    }
    Console.Write("Нажмите 'n' и Enter для выхода или любую клавишу и Enter для продолжения: ");
    op = Console.ReadLine();
    if (op.Length > 1) op = op.Substring(0, 1);
    if (op == "n") endApp = true;
    Console.WriteLine("\n");
   } while (!endApp);
   return;
  }
 }
}
19. Простой класс таймера на C#
using System;

namespace ConsoleApp1 {
 class Timer {
  private System.Diagnostics.Stopwatch t;
  private bool started;
  public void Start() {
   t = new System.Diagnostics.Stopwatch();
   t.Start();
   started = true;
  }
  public double Stop() {
   if (started) {
    t.Stop(); 
    started = false;
    return t.ElapsedMilliseconds;
   }
   return Double.NaN;
  }
 }
 class Program {
  static void Main(string[] args)  {
   Timer t = new Timer();
   Console.WriteLine("Сейчас запустим таймер и паузу в 1 сек.");
   t.Start();
   System.Threading.Thread.Sleep(1000);
   double time = t.Stop();
   Console.WriteLine("Измеренное время = "+time+" мс");
  }
 }
}

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

20. Вызываем функцию через заданный интервал времени
using System;

namespace ConsoleApp1 {
 class Program {
  private static void Callback(Object o)  {
   //Функция, которая будет вызываться с периодом
   Console.SetCursorPosition(0, 0);
   Console.WriteLine("{0:T}\n", DateTime.Now);
  }
  static void Main(string[] args)  {
   System.Threading.Timer t = 
    new System.Threading.Timer(Callback,null,0,1000);
    //вызывать Callback каждую секунду (1000 мс)
   Console.Clear();
   Console.CursorVisible = false;
   do { //остаёмся в цикле до нажатия Esc
    ConsoleKeyInfo key = Console.ReadKey(true);
    if (key.Key == ConsoleKey.Escape) break;
   } while (true);
   t.Dispose();
  }
 }
}

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

21. Простой класс "Прямоугольник"

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

using System;

namespace ConsoleApplication1 {
 class Rectangle {
  //Простейший класс "Прямоугольник" со сторонами, параллельными осям
  //координат, заданный координатами левого верхнего угла, шириной и высотой
  private double left = 0, top = 0, width = 0, height = 0;
  public Rectangle (double left, double top, double width, double height) {
   //корректируем данные и пишем из аргументов конструктора в объект this
   if (width < 0) { left += width; width = -width; }
   if (height < 0) { top += height; height = -height; }
   this.left = left; this.top = top; this.width = width; this.height = height;
  }
  public void Print (string header = null) {
   //Вывод заголовка header и координат 2 углов в консоль
   if (!String.IsNullOrEmpty (header)) {
    if (header.Trim ().Length > 0) Console.Write (header + " ");
   }
   Console.WriteLine ($"({left:F2},{top:F2}) - " +
    $"({(left+width):F2},{(top+height):F2})");
  }
  public double Area () { //Площадь
   return width  * height;
  }
  public double Perimeter () { //Периметр
   return 2 * (width + height);
  }
 }
 class Program {
  static void Main (string [] args) {
   Rectangle r = new Rectangle (-3,-5,-10,6); //Создали прямоугольник
   r.Print (); //Вывели координаты 2 углов
   Console.WriteLine ($"Area={r.Area():F2}, Perimeter={r.Perimeter ():F2}");
    //Вывели площадь и периметр
   Console.ReadKey ();
  }
 }
}
22. Перекодируем строку в UTF-8 и обратно

Проверить, какая у вас кодовая страница в запущенном окне консоли можно, нажав комбинацию клавиш Alt+Пробел, выбираем команду меню Свойства, далее в окне свойств выбираем вкладку Настройки, см. "Текущая кодовая страница".

В современных версиях Studio по умолчанию везде UTF-8, никаких телодвижений совершать не нужно.

Шрифт, установленный в это же окне консоли на одноимённой вкладке должен быть типа True Type и таким, что он поддерживает кодовую страницу Юникода UTF-8 (например, Consolas).

using System;
using System.Text;
using System.Text.RegularExpressions;

namespace ConsoleApp1 { 
 class Program {
	 static void Main () {
   Console.OutputEncoding = Encoding.GetEncoding (1251);
    //Кодовая страница "русская однобайтовая Windows" в консоль
   String Utf8String = ToUnicodeString ("Привет, это тест");
   Console.WriteLine (Utf8String);
   Console.WriteLine (FromUnicodeString (Utf8String));
   Console.ReadKey ();
  }
  static string FromUnicodeString (string str) {
   string toReturn = "";
   toReturn = Regex.Unescape (str);
   return toReturn;
  }

  static string ToUnicodeString (string str) {
   StringBuilder sb = new StringBuilder ();
   foreach (var c in str) {
    sb.Append ("\\u" + ( (int) c ).ToString ("X4"));
   }
   return sb.ToString ();
  }
 }
}
23. Получаем список папок и файлов рекурсивно

Метод GetList класса DirectoryList позволяет рекурсивно обойти вложенные папки, начиная с указанного пути. Для вложенных папок слева делается отступ в 2 пробела, чтобы список был древовидным.

Сохраняя данные в ArrayList или StringBuilder можно собрать информацию о нужных файлах и папках.

using System;
using System.IO;

namespace ConsoleApp1 { 
 class DirectoryList {
  private static void Print1 (string s, uint level) {
   //Делаем отступ слева для вложенных папок
   for (int i = 0; i < level; i++) Console.Write ("  ");
   Console.WriteLine (s);
  }
  public static void GetList (string Folder, uint level) {
   try {
    Print1 (Folder, level);
    foreach (string f in Directory.GetFiles (Folder)) {
      Print1 (f, level);
    }
    foreach (string d in Directory.GetDirectories (Folder)) {
     GetList (d, level + 1);
    }
   }
   catch (System.Exception e) {
    Console.WriteLine (e.Message);
   }
  }
 }
 class Program {
  static void Main () {
   DirectoryList.GetList ("D:\\Temp",0);
    //укажите свой путь к нужной папке
   Console.ReadLine ();
  }
 }
}

Заметим, что встроенной функцией возможен поиск по маске и во вложенных папках, по принципу

   try {
    Directory.GetFiles ("D:\\", "*.*", SearchOption.AllDirectories);
   }
   catch (System.Exception e) {
    Console.WriteLine (e.Message);
   }
24. Построчное чтение текстового файла

Продемонстрируем чтение строк из текстового файла test.txt в кодировке Юникода UTF-8, располагающегося в той же папке, что и файл Program.cs.

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

using System;
using System.IO;

class Example {
 static void Main () {
  StreamReader file = null;
  try {
   file = new StreamReader ("test.txt");
  }
  catch (Exception e) {
   Msg ("Ошибка: " + e.Message);
  }
  string line;
  do {
   line = file.ReadLine ();
   Console.WriteLine (line);
  }
  while (!file.EndOfStream);
  file.Close ();
  Msg ("Файл прочитан");
 }
 static void Msg (string msg) {
  Console.WriteLine (msg + "\nEnter для выхода");
  Console.ReadLine ();
  Environment.Exit (0); //выйти из приложения
 }
}
25. Построчная запись текстового файла

С помощью объекта класса StreamWriter и метода WriteLine, имеющего множество перегрузок, можно записать файл, содержащий необходимые данные. В качестве примера запишем в файл таблицу значений функции f(x) = x2 для значений x = 1, 1.5, 2, ..., 10.

using System;
using System.IO;

class Example {
 static void Main () {
  StreamWriter file = null;
  try {
   file = new StreamWriter ("test.txt");
  }
  catch (Exception e) {
   Msg ("Ошибка: " + e.Message);
  }
  string line;
  for (double x = 1.0; x <= 10.0; x+= 0.5) {
   file.WriteLine ($"{x,10:F2}{f(x),10:F2}");
  }
  file.Close ();
  Msg ("Файл записан");
 }
 static double f (double x) {
  return x * x;
 }
 static void Msg (string msg) {
  Console.WriteLine (msg + "\nEnter для выхода");
  Console.ReadLine ();
  Environment.Exit (0); //выйти из приложения
 }
}
26. Чтение массива чисел из текстового файла

Достаточно часто требуется получать из файла наборы числовых данных. Продемонстрируем чтение массива вещественных чисел отдельным примером.

Чтобы код получился компактным, мы прочитаем в строку всё содержимое файла методом ReadAllText, затем строковым методом Split получим набор лексем (слов) words, разделённых допустимыми разделителями (пробел, табуляция, перевод строки), наконец, с учётом российской локали методом TryParse "вытащим" все допустимые числа из words и добавим их в ArrayList, который затем легко преобразовать в "обычный" массив a. При таком подходе не произойдёт сбоя, если в файле есть не только допустимые лексемы и даже если нет ни одной допустимой.

using System;
using System.IO;
using System.Globalization;
using System.Collections;

class Example {
 static void Main () {
  string file = null;
  try {
   file = File.ReadAllText ("test.txt");
  }
  catch (Exception e) {
   Msg ("Ошибка: " + e.Message);
  }
  char [] separators = new char [] { ' ', '\t', '\r' };
  var words = file.Split (separators, StringSplitOptions.RemoveEmptyEntries);
  ArrayList arr = new ArrayList();
  double d;
  CultureInfo culture = CultureInfo.CreateSpecificCulture ("ru-RU");
  NumberStyles style = NumberStyles.Number | NumberStyles.AllowCurrencySymbol;
  foreach (var word in words) {
   bool b = double.TryParse (word, style, culture, out d);
   if (b) arr.Add (d);
  }
  var a = arr.ToArray ();
  foreach (double f in a) Console.Write ($"{f} ");
  Msg ($"\nФайл прочитан, всего чисел: {a.Length}");
 }
 static void Msg (string msg) {
  Console.WriteLine (msg + "\nEnter для выхода");
  Console.ReadLine ();
  Environment.Exit (0); //выйти из приложения
 }
}
27. Файл с произвольным доступом по байтам

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

using System;
using System.IO;

class Example {
 static void Main () {
  FileStream f = null;
  char ch;
  try {
   f = new FileStream ("test.dat", FileMode.Create);
   //Записать английский алфавит в файл:
   for (int i = 0; i < 26; i++) f.WriteByte ((byte) ( 'A' + i ));
   //Cчитать отдельные буквы английского алфавита из случайных позиций:
   Random r = new Random ();
   for (int i = 0; i < 5; i++) {
    int Pos = r.Next (26);
    f.Seek (Pos, SeekOrigin.Begin); //найти байт
    ch = (char) f.ReadByte ();
    Console.WriteLine ($"Символ номер {i}={ch}");
   }
  }
  catch (IOException e) {
   Msg ("Ошибка ввода-вывода: " + e.Message);
  }
  finally {
   if (f != null) f.Close ();
  }
  Msg ("Успешное завершение");
 }
 static void Msg (string msg) {
  Console.WriteLine (msg + "\nEnter для выхода");
  Console.ReadLine ();
  Environment.Exit (0); //выйти из приложения
 }
}
28. Чтение и запись кириллицы

Если требуется чтение и запись не отдельных байт, а символов Юникода (например, кириллицы), достаточно указать требуемую кодировку из перечисления System.Text.Encoding при создании потока StreamWriter или StreamReader.

В следующем примере мы применим оператор using для контроля объектов StreamWriter и StreamReader.

Создав поток записи FileStream, "отдадим" его объекту StreamWriter с указанием нужной кодировки. Затем запишем в файл 10 слов из случайных букв кириллицы.

Случайное слово генерирует метод RandomWord, которому аргументами передаётся допустимый алфавит letters (набор символов) и генератор случайных чисел rnd (если создавать генератор внутри метода, "случайность" получаемых строк может нарушиться).

После этого, аналогичным образом создав поток чтения и передав его в StreamReader, последовательно прочитаем все символы файла. Чтение файла продолжается до тех пор, пока метод Peek может проверить наличие в потоке чтения следующего символа. Получив методом Read целочисленный код символа, мы проверяем стандартным методом IndexOf, есть ли такой символ в алфавите, и если значение index оказывается допустимым, учитываем вхождение символа в частотной таблице freq.

По завершении чтении файла в консоль выводится частотная таблица символов алфавита letters.

using System;
using System.IO;
using System.Text;

class Example {
 static void Main () {
  char [] letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя".ToCharArray ();
  FileStream f = null;
  StreamWriter writer = null;
  Encoding Encoding = Encoding.UTF8;
  try {
   f = new FileStream ("test.txt", FileMode.Create, FileAccess.Write);
   writer = new StreamWriter (f, Encoding);
   Random  rnd = new Random ();
   using (writer) {
    for (int i = 0; i < 10; i++) writer.Write 
     (RandomWord (letters, rnd) +"\n");
   }
   writer.Close ();
   f.Close ();
  }
  catch (IOException e) {
   Msg ("Ошибка записи файла: " + e.Message);
  }
  finally {
   if (writer != null) writer.Close ();
   if (f != null) f.Close ();
  }
  StreamReader reader = null;
  int [] freq = new int [letters.Length];
  char first = letters [0];
  try {
   f = new FileStream ("test.txt", FileMode.Open, FileAccess.Read);
   reader = new StreamReader (f, Encoding);
   string symbols = new string (letters);
   using (reader) while (reader.Peek() > -1) {
    int c = reader.Read ();
    int index = symbols.IndexOf ((char) c);
    if (index > -1 & index < letters.Length) freq [index]++;
   }
   reader.Close ();
   f.Close ();
  }
  catch (IOException e) {
   Msg ("Ошибка чтения файла: " + e.Message);
  }
  finally {
   if (reader != null) reader.Close ();
   if (f != null) f.Close ();
  }
  for (int i = 0; i < letters.Length; i++) {
   Console.Write ($"{letters[i],1}: {freq[i],-3}");
  }
  Msg ("\nУспешное завершение");
 }
 static void Msg (string msg) {
  Console.WriteLine (msg + "\nEnter для выхода");
  Console.ReadLine ();
  Environment.Exit (0); //выйти из приложения
 }
 static string RandomWord (char [] letters, Random rnd) {
  int len = rnd.Next (3, 10);
  string word = "";
  for (int i = 0; i < len; i++) {
   int n = rnd.Next (0, letters.Length);
   word += letters [n];
  }
  return word;
 }
}
29. В заданной строке найти количество вхождений символа

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

using System;

class Example {
 static void Main () {
  Console.WriteLine ("Введите строку: ");
  string str = Console.ReadLine ().Trim();
  if (str.Length < 1) Msg ("Строка пуста");
  Console.WriteLine ("Введите символ: ");
  string chars = Console.ReadLine ().Trim ();
  if (chars.Length < 1) Msg ("Требуется хотя бы 1 символ");
  char c = char.Parse(chars.Substring (0, 1));
  if (Char.IsLetterOrDigit(c) == false) Msg ("Требуется буква или цифра");
  Console.WriteLine ();
  int cnt = CountNumberOfChars (str, c.ToString ());
  Msg ($"Найдено символов: {cnt}");
 }
 static void Msg (string msg) {
  Console.WriteLine (msg+ "\nEnter для выхода");
  Console.ReadLine ();
  Environment.Exit (0); //выйти из приложения
 }
 static int CountNumberOfChars (string str, string search) {
  //найдёт количество вождений подстроки search в строку str,
  //регистр символов игнорируется
  string s0 = str.ToUpper().Replace (search.ToUpper(), "");
  int count = (str.Length - s0.Length) / search.Length;
  return count;
 }
}
30. Самое длинное слово

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

using System;
using System.Text;

class Example {
 static void Main () {
  Console.WriteLine ("Введите строку: ");
  string str = Console.ReadLine ().Trim();
  if (str.Length < 1) Msg ("Строка пуста");
  char [] separators = new char [] { ' ', '\t', '\r' };
  string [] words = str.Split (separators, 
   StringSplitOptions.RemoveEmptyEntries); //удалить пустые слова
  string maxw = "";
  foreach (var word in words) {
   string w = Filter (word);
   if (w.Length > maxw.Length) maxw = w;
  }
  if (maxw.Length < 1) maxw = "Не найдено допустимых слов";
  Msg ($"Самое длинное слово: {maxw}");
 }
 static void Msg (string msg) {
  Console.WriteLine (msg+ "\nEnter для выхода");
  Console.ReadLine ();
  Environment.Exit (0); //выйти из приложения
 }
 static string Filter (string str) {
  StringBuilder res = new StringBuilder ();
  bool hyphen = false; //наличие дефиса
  int pos = 0; //счётчик позиций
  foreach (char c in str) {
   if (Char.IsLetterOrDigit (c) == true) res.Append (c); 
   else if (c == '-' & !hyphen) { //дефис кроме первой и последней позиций
    if (pos > 0 && pos < str.Length - 1) { res.Append (c); hyphen = true; }
   }
   pos++;
  }
  string resstr = res.ToString ();
  return resstr == "-" ? "" : resstr;
 }
}

Использование StringBuilder в данном случае экономит ресурсы, так как при операциях со String, например, при сложении строки с новым символом, каждый раз возвращается новая строка.

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

31. Преобразуем список списков строк в зубчатый массив

Преобразование выполняем с помощью System.Linq.

using System;
using System.Linq;
using System.Collections.Generic;

namespace ConsoleApplication1 {
 class Program {
  static void Main (string[] args) {
   List <List <string>> list = new List <List <string>> () {
    new List <string> () { "C++", "C#" },
    new List <string> () { "Java", "Javascript", "Python" }
   };
   string [][] arr = list.Select(row => row.ToArray()).ToArray();
   foreach (var row in arr)
    Console.WriteLine (String.Join("\t",row));
   Console.ReadLine ();
  }
 }
}
32. Преобразование строк в массивы и обратно
using System;

namespace ConsoleApplication1 {
 class Program {
  static void Main (string[] args) {
   string str = "Привет, я строка текста!";

   //1. Преобразовать строку в массив символов
   char [] chars = str.ToCharArray();
   foreach (char c in chars) Console.Write ($"{c} ");
   Console.WriteLine ();

   //2. Преобразовать массив обратно в строку
   string control = string.Join("",chars);
   Console.WriteLine (control);

   //... или для других типов данных и разделителей:
   string [] names = {"Билл","Том","Василий"};
   string namesStr = string.Join(",",names);
   Console.WriteLine (namesStr);
   double [] nums = {1.5,2.5,-3.5};
   string numsStr = string.Join(";",nums);
   Console.WriteLine (numsStr);

   //3. Получить из строки массив слов, разделённых
   //допустимыми разделителями separators
   char [] separators = {' ',',','!',':',';','?'};
   string [] words = 
    str.Split(separators,StringSplitOptions.RemoveEmptyEntries);
   foreach (string word in words) Console.WriteLine ($"{word}");
   Console.ReadLine ();
  }
 }
}
33. Рекурсивно сократить дробь x/y

Применяем рекурсивный алгоритм поиска НОД, числитель и знаменатель могут быть положительными или отрицательными.

//Сократить дробь вида x/y с помощью рекурсивного поиска
//наибольшего общего делителя для x и y (CGD)
using System;

namespace ConsoleApplication1 {
 class Program {

  ///Чтение целого числа из консоли с приглашением к вводу
  ///msg. Вернёт true или false (введено ли допустимое число),
  ///само число запишет в val
  public static bool ReadInt (string msg, ref int val) {
   Console.Write (msg);
   string input = Console.ReadLine ();
   bool result = int.TryParse (input, out int v);
   if (result) val = v;
   return result;
  }

  ///Рекурсивное сокращение дроби x/y. Новая дробь запишется
  ///как a/b
  public static void GCD (int x, int y, ref int a, ref int b) {
   if (y == 0) { a /= x; b /= x; return; }
   GCD (y, x % y, ref a, ref b);
  }
  static void Main (string[] args) {
   int x = 0,y = 0,a,b;
   Console.WriteLine ("Вводите дроби; не число - выход");
   do {
    if (ReadInt ("Числитель = ", ref x) == false ||
        ReadInt ("Знаменатель = ", ref y) == false) break;
    a = x; b = y; GCD (x, y, ref a, ref b);
    Console.WriteLine ($"{x}/{y}={a}/{b}");
   } while (true);
  }
 }
}
34. Максимум и второй максимум

В массиве целых чисел найти максимум и второй максимум, учитывая случай, когда все элементы массива одинаковы.

using System;

namespace ConsoleApp1 {
 class Program {
  static void Main () {
   const int n = 5;
   double [] arr = new double [n] { 4,5,5,5,5 };
   double max = Double.MinValue, max2 = Double.MinValue;
   foreach (var i in arr) {
    if (i > max) {
     max2 = max; max = i;
    }
    else if (i > max2 && i < max) max2 = i;
   }
   Console.WriteLine ($"max={max}");
   if (max2 != Double.MinValue) Console.WriteLine ($"max2={max2}");
   else Console.WriteLine ("max2 not found");
   Console.ReadLine ();
  }
 }
}   
35. Поменять местами 2 цифры целого числа

Поменять местами 2 произвольных цифры целого числа (положительного или отрицательного). Нумерация цифр выполняется слева направо, начиная с 1.

Проще всего применить преобразование числа в строку и обратно.

using System;

namespace ConsoleApp1 {
 class Program {
  static int Change2Digits (int n, int d1, int d2) {
   int d = Math.Abs(n), sign = Math.Sign(n);
   int l = d.ToString().Length-1;
   int pos1 = Math.Min(d1,d2)-1, pos2 = Math.Max(d1,d2)-1;
   if (pos1 < 0 || pos2 < 0 || pos1 > l || pos2 > l || pos1 == pos2) return n;
   string s = d.ToString ();
   string res = String.Empty;
   if (pos1 > 0) res += s.Substring (0, pos1);
   res += s.Substring (pos2, 1);
   if (pos2 > pos1 + 1) res += s.Substring (pos1+1, pos2-pos1-1);
   res += s.Substring (pos1, 1);
   if (pos2 < s.Length - 1) res += s.Substring (pos2+1);
   return sign*Convert.ToInt32(res);
  }
  static void Main () {
   int n = -12345;
   Console.WriteLine (Change2Digits (n, 1, 2));
   Console.WriteLine (Change2Digits (n, 1, 3));
   Console.WriteLine (Change2Digits (n, 2, 3));
   Console.WriteLine (Change2Digits (n, 4, 2));
   Console.ReadLine ();
  }
 }
}
36. Организовать XOR-шифрование введённой строки с использованием строгой дизъюнкции и её дешифровку
using System;

public class XORCipher {
 private string GetRepeatKey (string s, int n) {
  //генератор повторений пароля
  var r = s;
  while (r.Length < n) r += r;
  return r.Substring (0, n);
 }

 private string Cipher (string text, string secretKey) {
  //метод шифрования/дешифровки
  var currentKey = GetRepeatKey (secretKey, text.Length);
  var res = string.Empty;
  for (var i = 0; i < text.Length; i++) {
   res += ( (char) ( text [i] ^ currentKey [i] ) ).ToString ();
  }
  return res;
 }

 //шифрование текста
 public string Encrypt (string plainText, string password)
     => Cipher (plainText, password);

 //расшифровка текста
 public string Decrypt (string encryptedText, string password)
     => Cipher (encryptedText, password);
}

class Program {
 static void Main (string [] args) {
  var x = new XORCipher ();
  Console.Write ("Введите текст сообщения: ");
  var message = Console.ReadLine ();
  Console.Write ("Введите пароль: ");
  var pass = Console.ReadLine ();
  var encryptedMessageByPass = x.Encrypt (message, pass);
  Console.WriteLine ("Зашифрованное сообщение {0}", encryptedMessageByPass);
  Console.WriteLine ("Расшифрованное сообщение {0}", x.Decrypt (encryptedMessageByPass, pass));
  Console.ReadLine ();
 }
}
37. Организовать перевод введённого двоичного числа в десятичное и обратно

В этой задаче не разрешается использование стандартных функций, а только "ручная" реализация алгоритма.

using System;
using System.Text;

class Program {
 static uint BinaryToDecimal (string binaryNumber) {
  var exp = 1u;
  var result = 0u;
  for (var i = binaryNumber.Length - 1; i >= 0; i--) {
   if (binaryNumber [i] == '1') result += exp;
   //Всё, что не 1, будет нулями
   exp *= 2;
  }
  return result;
 }
 static string DecimalToBinary (uint decimalNumber) {
  var binaryNumber = string.Empty;
  while (decimalNumber > 0) {
   binaryNumber = ( decimalNumber % 2 ) + binaryNumber;
   decimalNumber /= 2;
  }
  return binaryNumber;
 }

 static void Main (string [] args) {
  Console.OutputEncoding = Encoding.UTF8;
  Console.Write ("Введите число в двоичной системе: ");
  var binNum = Console.ReadLine ();
  var decNum = BinaryToDecimal (binNum);
  Console.WriteLine ("{0} => {1}", binNum, decNum);
  binNum = DecimalToBinary (decNum);
  Console.WriteLine ("{0} => {1}", decNum, binNum);
  Console.ReadLine ();
 }
}
38. Организовать перевод введённого троичного числа в десятичное и обратно

С тем же ограничением, что в предыдущей задаче.

using System;
using System.Text;

class Program {
 static uint TernaryToDecimal (string ternaryNumber) {
  var exponent = 0;
  var result = 0u;
  for (var i = ternaryNumber.Length - 1; i >= 0; i--) {
   var ternaryDigit = Convert.ToUInt32 (ternaryNumber [i].ToString ());
   if (ternaryDigit > 2) return 0; //только 0, 1 или 2
   result += ternaryDigit * Convert.ToUInt32 (Math.Pow (3, exponent));
   exponent++;
  }
  return result;
 }
 static string DecimalToTernary (uint decimalNumber) {
  var ternaryNumber = string.Empty;
  while (decimalNumber > 0) {
   ternaryNumber = ( decimalNumber % 3 ) + ternaryNumber;
   decimalNumber /= 3;
  }
  return ternaryNumber;
 }

 static void Main (string [] args) {
  Console.OutputEncoding = Encoding.UTF8;
  Console.Write ("Введите число в троичной системе: ");
  var ternaryNum = Console.ReadLine ();
  var decimalNum = TernaryToDecimal (ternaryNum);
  Console.WriteLine ("{0} => {1}", ternaryNum, decimalNum);
  ternaryNum = DecimalToTernary (decimalNum);
  Console.WriteLine ("{0} => {1}", decimalNum, ternaryNum);
  Console.ReadLine ();
 }
}
39. Реализовать решето Эратосфена для поиска простых чисел, не превышающих заданного N

Алгоритм в "Вики".

Так как мы будем удалять элементы списка, а массивы не позволяют этого, для хранения данных используем класс List из библиотеки System.Collections.Generic.

На .NET это довольно медленно, как и всё на нём.

using System;
using System.Collections.Generic;

class Program {
 static List<uint> SieveEratosthenes (uint n) {
  var numbers = new List<uint> ();
  //заполнение списка числами от 2 до n-1
  for (var i = 2u; i < n; i++) numbers.Add (i);
  for (var i = 0; i < numbers.Count; i++) {
   for (var j = 2u; j < n; j++) {
    //удаляем кратные числа из списка
    numbers.Remove (numbers [i] * j);
   }
  }
  return numbers;
 }
 static void Main (string [] args) {
  Console.Write ("N = ");
  var n = Convert.ToUInt32 (Console.ReadLine ());
  var primeNumbers = SieveEratosthenes (n);
  Console.WriteLine ("Простые числа до заданного {0}:", n);
  Console.WriteLine (string.Join (", ", primeNumbers));
  Console.ReadLine ();
 }
}

09.01.2021, 18:29 [39814 просмотров]


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

показать комментарии (1)