LEARN X · ЗА 17 МИН
C#
Весь C# на одной странице: типы, строки, LINQ, ООП, async/await и дженерики — экспресс-тур в закомментированном коде.
C# ("си шарп") — статически типизированный язык от Microsoft для платформы .NET. Здесь весь язык на одной странице: учим прямо по комментариям в коде.
Структура программы
Точка входа и вывод в консоль.
using System; // подключаем пространство имён (типы вроде Console)
using System.Collections.Generic; // ещё одно: List<T>, Dictionary<K,V>
namespace MyApp // пространство имён группирует код
{
class Program // в C# весь код живёт внутри классов
{
// Main — точка входа; args — аргументы командной строки
static void Main(string[] args)
{
Console.WriteLine("Привет, C#!"); // печать + перевод строки
Console.Write("без перевода"); // печать без \n
}
}
}
// В C# 10+ можно top-level statements — без класса и Main:
// Console.WriteLine("Привет!"); // файл сам становится телом Main
Переменные и типы
Значимые (value) копируются, ссылочные (reference) делят объект.
int i = 42; // целое 32 бита
long big = 9000000000; // целое 64 бита
double d = 3.14; // число с плавающей точкой
decimal money = 9.99m; // точная десятичная (деньги), суффикс m
bool flag = true; // логический: true / false
char c = 'A'; // один символ в одинарных кавычках
string s = "текст"; // строка (ссылочный тип)
var x = 10; // var — тип выводится компилятором (тут int)
const double PI = 3.14159; // константа, менять нельзя
int? maybe = null; // nullable value-тип: int или null
int val = maybe ?? 0; // ?? — значение по умолчанию, если null // val == 0
// Value-тип: копируется значение
int a = 5; int b = a; b = 99; // a по-прежнему 5
// Reference-тип: копируется ссылка на тот же объект
int[] arr1 = { 1, 2, 3 };
int[] arr2 = arr1; arr2[0] = 100; // arr1[0] тоже стало 100!
Строки
Интерполяция, методы и эффективная склейка через StringBuilder.
string name = "Аня";
int age = 25;
string greet = $"Привет, {name}, тебе {age}"; // интерполяция $""
string sum = $"2 + 2 = {2 + 2}"; // "2 + 2 = 4"
string path = @"C:\temp\file"; // @ — verbatim: без экранирования \
// Полезные методы:
"Hello".Length; // 5
"Hello".ToUpper(); // "HELLO"
"Hello".ToLower(); // "hello"
" hi ".Trim(); // "hi"
"a,b,c".Split(','); // ["a", "b", "c"]
string.Join("-", 1, 2, 3); // "1-2-3"
"Hello".Contains("ell"); // true
"Hello".Replace("l", "L"); // "HeLLo"
"Hello".Substring(1, 3); // "ell"
"Hello"[0]; // 'H' — индексатор
// StringBuilder — для многих склеек (строки неизменяемы!)
using System.Text;
var sb = new StringBuilder();
sb.Append("раз");
sb.Append("-два");
sb.AppendLine("-три");
string result = sb.ToString(); // "раз-два-три\n"
Операторы и условия
int n = 7;
// if / else if / else
if (n > 10)
Console.WriteLine("много");
else if (n > 5)
Console.WriteLine("средне"); // сюда
else
Console.WriteLine("мало");
// Тернарный оператор: условие ? если-да : если-нет
string parity = n % 2 == 0 ? "чёт" : "нечёт"; // "нечёт"
// Логические: && (и), || (или), ! (не)
bool ok = n > 0 && n < 100;
// Классический switch
switch (n)
{
case 1:
Console.WriteLine("один");
break; // break обязателен
case 7:
case 8:
Console.WriteLine("7 или 8"); // сюда
break;
default:
Console.WriteLine("другое");
break;
}
// switch expression (C# 8+) — компактно, возвращает значение
string word = n switch
{
1 => "один",
7 => "семь", // сюда -> word == "семь"
_ => "другое" // _ — всё остальное (default)
};
Циклы
// for — счётчик
for (int i = 0; i < 3; i++)
Console.WriteLine(i); // 0, 1, 2
// foreach — перебор коллекции
foreach (var item in new[] { "a", "b", "c" })
Console.WriteLine(item); // a, b, c
// while — пока условие истинно
int k = 0;
while (k < 3)
{
Console.WriteLine(k);
k++; // 0, 1, 2
}
// do-while — тело хотя бы раз, проверка в конце
int m = 0;
do
{
Console.WriteLine(m);
m++;
} while (m < 3); // 0, 1, 2
// break — выйти из цикла; continue — к следующей итерации
for (int i = 0; i < 10; i++)
{
if (i == 5) break; // остановиться на 5
if (i % 2 == 0) continue; // пропустить чётные
Console.WriteLine(i); // 1, 3
}
Массивы и коллекции
// Массив — фиксированный размер
int[] nums = new int[3]; // [0, 0, 0]
int[] primes = { 2, 3, 5, 7 };
primes[0]; // 2
primes.Length; // 4
// List<T> — динамический список
var list = new List<string> { "a", "b" };
list.Add("c"); // ["a", "b", "c"]
list.Remove("a"); // ["b", "c"]
list.Count; // 2
list.Contains("b"); // true
// Dictionary<K, V> — ключ -> значение
var ages = new Dictionary<string, int>
{
["Аня"] = 25,
["Боб"] = 30
};
ages["Аня"]; // 25
ages.ContainsKey("Боб"); // true
ages["Вика"] = 22; // добавить
if (ages.TryGetValue("Аня", out int v)) { /* v == 25 */ }
// HashSet<T> — множество уникальных значений
var set = new HashSet<int> { 1, 2, 2, 3 }; // {1, 2, 3}
set.Add(2); // false — уже есть
set.Contains(3); // true
Методы
Параметры, перегрузка, ref/out, опциональные аргументы и лямбды.
// Обычный метод: тип возврата, имя, параметры
int Add(int a, int b) => a + b; // => expression body
// Опциональные параметры (со значением по умолчанию)
int Power(int x, int exp = 2) => (int)Math.Pow(x, exp);
Power(5); // 25 (exp = 2)
Power(2, 3); // 8
// Именованные аргументы
Power(exp: 3, x: 2); // 8
// Перегрузка — одно имя, разные сигнатуры
string Describe(int n) => $"число {n}";
string Describe(string s) => $"строка {s}";
// ref — передать по ссылке (метод меняет переменную вызывающего)
void Double(ref int x) => x *= 2;
int y = 5; Double(ref y); // y == 10
// out — вернуть несколько значений
void MinMax(int[] a, out int min, out int max)
{
min = a.Min(); max = a.Max();
}
MinMax(new[] { 3, 1, 9 }, out int lo, out int hi); // lo=1, hi=9
// params — переменное число аргументов
int Sum(params int[] xs) => xs.Sum();
Sum(1, 2, 3, 4); // 10
// Лямбды (анонимные функции)
Func<int, int> square = x => x * x; // принимает и возвращает
square(4); // 16
Action<string> log = msg => Console.WriteLine(msg); // ничего не возвращает
Классы и ООП
Поля, свойства (property), конструктор и инкапсуляция.
class Person
{
// Приватное поле (инкапсуляция: скрыто от внешнего кода)
private int _age;
// Авто-свойство: компилятор сам создаёт скрытое поле
public string Name { get; set; }
// Свойство с логикой в set
public int Age
{
get => _age;
set => _age = value < 0 ? 0 : value; // защита от отрицательных
}
// Свойство только для чтения (вычисляемое)
public bool IsAdult => _age >= 18;
// Конструктор — инициализация при создании объекта
public Person(string name, int age)
{
Name = name;
Age = age;
}
// Метод
public string Greet() => $"Я {Name}, мне {Age}";
}
// Использование:
var p = new Person("Аня", 25);
p.Name; // "Аня"
p.Greet(); // "Я Аня, мне 25"
p.IsAdult; // true
p.Age = -5; // сеттер защитит -> Age == 0
// Object initializer — задать свойства при создании
var p2 = new Person("Боб", 30) { Name = "Боря" };
Наследование и интерфейсы
// Базовый класс
class Animal
{
public string Name { get; set; }
// virtual — метод можно переопределить в наследниках
public virtual string Sound() => "...";
}
// Наследник : базовый класс
class Dog : Animal
{
// override — переопределяем virtual-метод
public override string Sound() => "Гав";
}
Animal a = new Dog { Name = "Рекс" };
a.Sound(); // "Гав" — полиморфизм (вызвался метод Dog)
// abstract — нельзя создать экземпляр, только наследовать
abstract class Shape
{
public abstract double Area(); // без тела — обязан реализовать наследник
}
class Circle : Shape
{
public double R;
public override double Area() => Math.PI * R * R;
}
// Интерфейс — контракт (что класс умеет, без реализации)
interface IMovable
{
void Move(int dx, int dy); // только сигнатура
}
class Player : IMovable // класс обязуется реализовать Move
{
public int X, Y;
public void Move(int dx, int dy) { X += dx; Y += dy; }
}
// Класс может реализовать несколько интерфейсов (в отличие от наследования)
Записи и структуры
// record — неизменяемый класс с авто-равенством (C# 9+)
record Point(int X, int Y);
var p1 = new Point(1, 2);
var p2 = new Point(1, 2);
p1 == p2; // true — сравнение по значениям, не по ссылке!
var p3 = p1 with { Y = 9 }; // with — копия с изменением: Point(1, 9)
// struct — значимый тип (копируется целиком, лежит в стеке)
struct Vector
{
public double X, Y;
public double Length => Math.Sqrt(X * X + Y * Y);
}
var v = new Vector { X = 3, Y = 4 };
v.Length; // 5
// enum — перечисление именованных констант
enum Status { Active, Paused, Done } // 0, 1, 2
Status st = Status.Active;
if (st == Status.Active) { /* ... */ }
(int)Status.Done; // 2 — приведение к числу
Исключения
try
{
int x = int.Parse("не число"); // бросит FormatException
}
catch (FormatException ex) // ловим конкретный тип
{
Console.WriteLine($"Ошибка формата: {ex.Message}");
}
catch (Exception ex) // ловим всё остальное
{
Console.WriteLine($"Прочее: {ex.Message}");
}
finally
{
// выполнится ВСЕГДА (и при ошибке, и без): закрыть файл и т.п.
Console.WriteLine("finally");
}
// Бросить исключение самому
int Divide(int a, int b)
{
if (b == 0)
throw new DivideByZeroException("делить на ноль нельзя");
return a / b;
}
// Своё исключение
class MyException : Exception
{
public MyException(string msg) : base(msg) { }
}
LINQ
Запросы к коллекциям: фильтрация, проекция, сортировка, группировка.
using System.Linq;
var nums = new[] { 5, 3, 8, 1, 9, 2 };
// Where — фильтр (оставить подходящие)
var big = nums.Where(x => x > 4); // 5, 8, 9
// Select — проекция (преобразовать каждый элемент)
var squares = nums.Select(x => x * x); // 25, 9, 64, ...
// OrderBy / OrderByDescending — сортировка
var sorted = nums.OrderBy(x => x); // 1, 2, 3, 5, 8, 9
// Цепочки + материализация
var top = nums.Where(x => x > 3)
.OrderByDescending(x => x)
.Take(2)
.ToList(); // [9, 8]
// Агрегаты
nums.Sum(); // 28
nums.Max(); // 9
nums.Average(); // 4.66...
nums.Count(x => x % 2 == 0); // 3 — сколько чётных
nums.Any(x => x > 8); // true
nums.First(x => x > 4); // 5
// GroupBy — группировка
var words = new[] { "кот", "корова", "пёс", "пони" };
var groups = words.GroupBy(w => w[0]); // по первой букве
foreach (var g in groups)
Console.WriteLine($"{g.Key}: {g.Count()}"); // к: 2, п: 2
// Альтернативный синтаксис запроса
var q = from x in nums where x > 3 orderby x select x;
async/await и обобщения
Дженерики (<T>) для типобезопасного переиспользования и асинхронность.
using System.Threading.Tasks;
// Обобщённый метод: T — любой тип, определяется при вызове
T First<T>(T[] items) => items[0];
First(new[] { 10, 20 }); // 10 (T = int)
First(new[] { "a", "b" }); // "a" (T = string)
// Обобщённый класс
class Box<T>
{
public T Value { get; set; }
public Box(T value) => Value = value;
}
var box = new Box<int>(42);
box.Value; // 42
// Ограничение типа: where T : ... (T должен наследовать/реализовать)
T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) > 0 ? a : b;
Max(3, 7); // 7
// async / await — асинхронность без блокировки потока
async Task<int> LoadAsync()
{
await Task.Delay(100); // ждём, не блокируя поток
return 42; // результат оборачивается в Task<int>
}
async Task UseAsync()
{
int result = await LoadAsync(); // await разворачивает Task<int> -> int
Console.WriteLine(result); // 42
}
// Task — операция без результата; Task<T> — с результатом T
// Несколько задач параллельно:
// await Task.WhenAll(LoadAsync(), LoadAsync());