Примеры консольных приложений на 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
09.01.2021, 18:29; рейтинг: 57