tgindex
C#Hive: Projects & Progress | Программирование

C#Hive: Projects & Progress | Программирование

Статистика
@CSharpHiveрусский

Сообщество единомышленников C#: решаем задачи, учимся, развиваемся и общаемся вместе. Советы по работе на фрилансе, готовые проекты, код ревью, рекомендации и исследования. Вопросы/сотрудничество: @tel_phil9

Последний пост
15 окт. 2025 г.
Последнее чтение
12 авг.
Постов за неделю
0
Всего постов
50
Тип
открытый
Язык
русский
В каталоге с
12 авг.
Подписчики
1 637
−4 за 4 дн.
Сутки
−1
−0,06%
Неделя
 
Месяц
 
Просмотров на пост
727
40 постов
Вовлечённость
44,4%
к подписчикам
Постов в день
0,0
всего 50
Упоминаний
0
каналов
Охват размещения
оценка
1/24сутки в ленте
1/48двое суток
1/72трое суток

Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.

Посты

  • 🖥 Кодовый замок Имеется кодовый замок, состоящий из 4 цифр. Требуется расшифровать код 🔐✅✅✅✅, пользуясь подсказками комбинаций ниже. 4️⃣3️⃣8️⃣6️⃣ Одна цифра верна, но её расположение ошибочно. 1️⃣4️⃣5️⃣7️⃣ Две цифры верны, но их расположение ошибочно. 6️⃣3️⃣0️⃣1️⃣ Одна цифра верна, её расположение корректно. 9️⃣6️⃣0️⃣7️⃣ Полностью неверно. 8️⃣6️⃣3️⃣2️⃣ Две цифры верны, но их расположение ошибочно. Свои варианты и размышления пишите в комментариях, будет интересно почитать! #Полезно #Собеседование

  • видео или голосовое, без подписи

  • #Опрос

  • 🖥 Функциональное внедрение через Ambient Context Продолжаем разбор прошлой темы. Можно выделить три способа для внедрения зависимости в объект: 1. Constructor Injection. Зависимости передаются классу через его конструктор; 2. Property Injection. Зависимости передаются классу через публичные свойства после создания объекта. Способ предоставляет необязательные зависимости и позволяет менять их во время выполнения без создания новых экземпляров; 3. Method Injection. Зависимость вводится в метод, который будет её использовать. Идея же Ambient Context заключается в создании публичного статического свойства или метода, предоставляющих экземпляр объекта определённого типа и доступного любому объекту в приложении. Паттерн позволяет предоставлять зависимости функциям, не передавая их явно в качестве параметров. Он особенно полезен для обработки сквозных задач в функциональном стиле. Предположим, у нас есть класс логирования со следующей функцией: public static class Logger { public static void Print(string message) { string data = $"[{DateTime.Now}] {message}"; Console.WriteLine(data); } } Представим, что мы хотим иметь возможность изменять способ логирования (например, записывать в файл) без изменения сигнатуры функции Print. Решить такую задачу можно примерно следующим кодом: public static class Logger { public static Action<string> PrintHandler { set => _printHandler = value; } static Logger() => sync = new(); private static Action<string> _printHandler; private static readonly object sync; public static void Print(string message) { lock (sync) { string data = $"[{DateTime.Now}] {message}"; (_printHandler ?? Console.WriteLine)(data); } } } Используем: Logger.PrintHandler = (msg) => File.AppendAllText("Log.txt", $"{msg}\r\n"); Logger.Print("This message will be written to file!"); Logger.PrintHandler = null; Logger.Print("This message will be printed to console!"); Добавив делегат PrintHandler, отвечающий за сквозную реализацию, любой источник извне может корректировать реализацию метода Print. Если обработчик не трогать, то класс по умолчанию выведет сообщение в консоль. Первое сообщение логируется в файл, второе же будет выведено в консоль. Гибкость и расширяемость такого способа ясна, а подмена зависимости для тестов сильно упростит тестирование. Однако есть и минусы: сложность в отслеживании реализации зависимости, понимание кода может быть затруднено и необходимо аккуратно управлять контекстом из-за непредсказуемого поведения. #Полезно #DependencyInjection #Паттерн

  • Решение задачи к посту. int MajorityElement(int[] items) { var hashtable = new Hashtable(); for (int i = 0; i < items.Length; i++) { int item = items[i]; hashtable[item] = hashtable.ContainsKey(item) ? (int)hashtable[item] + 1 : hashtable[item] = 1; if ((int)hashtable[item] > items.Length / 2) return item; } return -1; } #Задача #Решение #Полезно

  • 🖥 Задача: алгоритм большинства голосов (⭐️) Написать метод, который принимает массив размером N, находит мажорирующий элемент (который встречается более N/2 раз) и возвращает его. Допускается только один проход по массиву, чтобы определить элемент большинства. Ниже приведены примеры. int[] array = { 2, 12, 2, 2, 2, 4, 2 }; MajorityElement(array); 👉 2 int[] array = { 3, 3, 4, 2, 4, 4, 2, 4, 4 }; MajorityElement(array); 👉 4 int[] array = { 5, 5, 7, 7, 9, 9 }; MajorityElement(array); 👉 -1 int[] array = { 4, 3, 4 }; MajorityElement(array); 👉 4 int[] array = { 1, 1, 1, 1, 1, 1 }; MajorityElement(array); 👉 1 Пишите варианты в комментариях. Решение будет сегодня вечером новым постом в канале. #Задача #Lvl1

  • 🖥 Внедрение зависимостей Dependency injection (DI), или внедрение зависимостей, представляет механизм, который позволяет сделать компоненты программы слабосвязанными, а всю программу в целом более гибкой, более адаптируемой и расширяемой. В центре подобного механизма находится зависимость, то есть некоторая сущность, от которой зависит другая сущность. Например: var logger = new Logger(); logger.Print("Hello world!"); public class Logger { private readonly DefaultLogService logService = new DefaultLogService(); public void Print(string message) => logService.Write($"[{DateTime.Now}] {message}"); } public class DefaultLogService { public void Write(string message) => Console.WriteLine(message); } Здесь имеется класс Logger — условный тип, который логирует некоторое сообщение с помощью метода Print. При логировании Logger добавляет к сообщению дату. Для логирования Logger использует дополнительный класс DefaultLogService, который непосредственно управляет, как и куда будет логироваться сообщение. В данном случае, с помощью метода Write он просто выводит сообщение в консоль. При выполнении программы, как и ожидается, мы увидим из консоли сообщение: [28.09.2025 22:43:00] Hello world! Программа прекрасно работает. Тем не менее, в дальнейшем мы можем столкнуться с рядом проблем. Прежде всего, класс Logger жёстко привязан к классу DefaultLogService. Если мы захотим вместо DefaultLogService использовать другой тип логера, например, логировать в файл, а не в консоль, то нам придётся менять класс Logger. Поменять один класс не составит труда, но в проекте может быть множество классов, которые используют DefaultLogService, что уже будет труднее. Кроме того, класс DefaultLogService может иметь свои зависимости, которые тоже может потребоваться поменять. В итоге, такими системами сложнее управлять и их тестировать. Отвяжем объект Logger от класса DefaultLogService, написав интерфейс, который будет представлять сервис логера, и передавать его извне в объект Logger: var logger = new Logger(new DefaultLogService()); logger.Print("Hello world!"); logger = new Logger(new GreenLogService()); logger.Print("Hello world!"); public interface ILogService { void Write(string message); } public class DefaultLogService : ILogService { public void Write(string message) => Console.WriteLine(message); } public class GreenLogService : ILogService { public void Write(string message) { var defaultColor = Console.ForegroundColor; try { Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine(message); } finally { Console.ForegroundColor = defaultColor; } } } public class Logger { public Logger(ILogService logService) => service = logService ?? throw new ArgumentNullException(nameof(logService)); private readonly ILogService service; public void Print(string message) => service.Write($"[{DateTime.Now}] {message}"); } Теперь класс Logger не зависит от конкретной реализации класса DefaultLogService, теперь это может быть любая реализация интерфейса ILogService. Кроме того, создание сервиса логера выносится во внешний код. Класс Logger больше ничего не знает о сервисе кроме того, что у него есть метод Write, который позволяет логировать сообщение куда-то каким-то образом. Тем не менее, остаётся проблема управления подобными зависимостями, особенно это касается больших приложений. Если у нас десятки классов, которые через конструктор получают сервис логирования и мы хотим, чтобы они логировали в едином стиле (на консоль, в файл, отправка email и т.д.), то нам снова придётся менять вызов конструктора этих классов. #Полезно #DependencyInjection #Паттерн

  • видео или голосовое, без подписи

  • #Опрос

  • #Юмор

  • Решение задачи к посту. int BinaryGCD(int a, int b) { int k = 1; while (a != 0 && b != 0) { while (a % 2 == 0 && b % 2 == 0) { a /= 2; b /= 2; k *= 2; } while (a % 2 == 0) a /= 2; while (b % 2 == 0) b /= 2; if (a >= b) a -= b; else b -= a; } return b * k; } #Задача #Решение #Полезно

  • 🖥 Задача: бинарный алгоритм вычисления НОД (⭐️) Написать метод, который реализует нахождение наибольшего общего делителя (НОД) между двумя значениями, используя бинарный алгоритм, и возвращает результат. Ниже приведены примеры. BinaryGCD(a: 24, b: 18); 👉 6 BinaryGCD(223744, 300181); 👉 19 BinaryGCD(148764, 10308); 👉 12 BinaryGCD(808, 1212); 👉 404 BinaryGCD(111, 17); 👉 1 Пишите варианты в комментариях. Решение будет сегодня вечером новым постом в канале. #Задача #Lvl1

  • 🖥 Ключевые слова checked и unchecked Имеется средство, связанное с генерированием исключений, возникающих при переполнении в арифметических вычислениях. Как известно, результаты некоторых видов арифметических вычислений могут превышать диапазон представления чисел для типа данных, используемого в вычислении. В этом случае происходит так называемое переполнение. Допускается указывать, будет ли в коде генерироваться исключение при переполнении, с помощью ключевых слов checked и unchecked. Так, если требуется указать, что выражение будет проверяться на переполнение, следует использовать ключевое слово checked, а если требуется проигнорировать переполнение — ключевое слово unchecked. В последнем случае результат усекается, чтобы не выйти за пределы диапазона представления чисел для целевого типа выражения. У ключевого слова checked имеются две общие формы. В одной форме проверяется конкретное выражение, поэтому она называется операторной. В другой форме проверяется блок операторов, поэтому она называется блочной. Ниже приведены обе формы: checked(выражение) checked { // Проверяемые операторы // ... } Если вычисление проверяемого выражения или блока приводит к переполнению, то генерируется исключение OverflowException. У ключевого слова unchecked также имеются две общие формы. Их синтаксис имеет ту же основу: unchecked(выражение) unchecked { // Операторы, для которых переполнение игнорируется // ... } Если же в непроверяемом выражении или блоке происходит переполнение, то результат его вычисления усекается. Используем unchecked в одном выражении: int maxInt = int.MaxValue; int uncheckedResult = unchecked(maxInt + 1); Console.WriteLine(uncheckedResult); Вывод с урезанным результатом: -2147483648 Используем checked для всего блока операторов: checked { try { int maxInt = int.MaxValue; int checkedResult = maxInt + 1; Console.WriteLine(checkedResult); } catch (OverflowException e) { Console.WriteLine($"Переполнение: {e.Message}"); } } Вывод: Переполнение: Arithmetic operation resulted in an overflow. Использовать checked уместно там, где переполнение может привести к критическим ошибкам в логике нашего приложения. #Полезно #Exception #TryCatch

  • видео или голосовое, без подписи

  • #Опрос

  • 🖥 Атрибут CallerArgumentExpression Атрибут может быть использован для того, чтобы указать компилятору на необходимость захвата строкового представления выражения аргумента, переданного методу. Например: using System.Runtime.CompilerServices; void Function( int value1, TimeSpan value2, [CallerArgumentExpression("value1")] string? expression1 = null, [CallerArgumentExpression("value2")] string? expression2 = null) { Console.WriteLine($"{expression1} = {value1}"); Console.WriteLine($"{expression2} = {value2}"); } Нас интересует вызов вышеобъявленной функции: int myInt = 42; TimeSpan myTime = TimeSpan.Zero; Function(myInt, myTime); Вывод: myInt = 42 myTime = 00:00:00 Самое интересное происходит во время компиляции: Function(1, default); Console.WriteLine(); int x = 1; TimeSpan y = TimeSpan.Zero; Function(x, y); Console.WriteLine(); Function(int.Parse("2") + 1 + Math.Max(2, 3), TimeSpan.MaxValue - TimeSpan.Zero); Console.WriteLine(); Вывод: 1 = 1 default = 00:00:00 x = 1 y = 00:00:00 int.Parse("2") + 1 + Math.Max(2, 3) = 6 TimeSpan.MaxValue - TimeSpan.Zero = 10675199.02:48:05.4775807 Параметр функции expression1 записывается с помощью [CallerArgumentExpression("value1")]. В результате, при вызове функции компилятор возьмёт выражение, переданное в value1 и использует текст этого выражения для expression1. И, аналогично, текст выражения, использованного для value2, будет использован для expression2. Это полезно для: ⏺Тестирования, ибо позволяет получить имена тестируемых переменных и выражений; ⏺Реализации более информативных сообщений при логировании; ⏺Упрощения отладки кода. #Полезно #Attribute

  • Решение задачи к посту. public class KnapsackProblem { public Item[] Items { get; } public int MaxWeight { get; } public Backpack BestBackpack { get; } private KnapsackProblem(Item[] items, int maxWeight) { Items = items; MaxWeight = maxWeight; BestBackpack = GetBestBackpack(); } public static KnapsackProblem Solve(Item[] items, int maxWeight) { if (items == null) throw new ArgumentNullException(nameof(items)); if (items.Length < 1) throw new ArgumentOutOfRangeException(nameof(items)); if (maxWeight < 0) throw new ArgumentOutOfRangeException(nameof(maxWeight)); return new(items, maxWeight); } public override string ToString() => $"Наибольшая ценность при весе {BestBackpack.Items.Sum(x => x.Weight)} кг: {BestBackpack}"; private Backpack GetBestBackpack() { int rows = Items.Length + 1; int columns = MaxWeight + 1; var backpacks = BackpackOptions(rows, columns); return backpacks[rows - 1, columns - 1]; } private Backpack[,] BackpackOptions(int rows, int columns) { Backpack[,] backpacks = new Backpack[rows, columns]; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { if (i == 0 || j == 0) { backpacks[i, j] = new(); continue; } var item = Items[i - 1]; if (i == 1) { backpacks[i, j] = item.Weight <= j ? new(item) : new(); continue; } var prevBackpack = backpacks[i - 1, j]; if (item.Weight > j) { backpacks[i, j] = prevBackpack; continue; } int budget = item.Price + backpacks[i - 1, j - item.Weight].Budget; if (prevBackpack.Budget > budget) { backpacks[i, j] = prevBackpack; continue; } Backpack backpack = new(item); backpacks[i - 1, j - item.Weight].Items.ForEach(x => backpack.AddItem(x)); backpacks[i, j] = backpack; } } return backpacks; } } #Задача #Решение #Полезно

  • 🖥 Задача: проблема рюкзака (⭐️⭐️) Дано два класса: public class Item { public string Name { get; } public int Weight { get; } public int Price { get; } public Item(string name, int weight, int price) { Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentNullException(nameof(name)) : name; Weight = weight < 0 ? throw new ArgumentOutOfRangeException(nameof(weight)) : weight; Price = price < 0 ? throw new ArgumentOutOfRangeException(nameof(price)) : price; } public override string ToString() => Name; } public class Backpack { public int Budget { get; private set; } public List<Item> Items { get; } public Backpack() => Items = new(); public Backpack(Item item) { Items = new(); AddItem(item); } public override string ToString() => Items.Count == 0 ? Budget.ToString() : $"{Budget} = {string.Join(" + ", Items.Select(x => x.Name))}"; public void AddItem(Item item) { Items.Add(item ?? throw new ArgumentNullException(nameof(item))); Budget += item.Price; } } Необходимо написать класс для решения задачи о рюкзаке, который будет выбирать вещи с наибольшей общей стоимостью, соблюдая ограничение вместимости рюкзака. Ниже приведены примеры. Item[] items = { new("Ноутбук", 3, 2000), new("Гитара", 1, 1500), new("Бензопила", 4, 3000) }; KnapsackProblem knapsackProblem = KnapsackProblem.Solve(items, maxWeight: 4); Console.WriteLine(knapsackProblem); 👉 Наибольшая ценность при весе 4 кг: 3500 = Гитара + Ноутбук Item[] items = { new("Ноутбук", 2, 40000), new("Книга", 1, 600), new("Бинокль", 2, 5000), new("Аптечка", 4, 1500), new("Котелок", 1, 500) }; int maxWeight = 8; KnapsackProblem kp = KnapsackProblem.Solve(items, maxWeight); Backpack backpack = kp.BestBackpack; Console.WriteLine(backpack); 👉 46500 = Аптечка + Бинокль + Ноутбук Item[] items = { new("1", 5, 3), new("2", 10, 5), new("3", 6, 4), new("4", 5, 2) }; KnapsackProblem knapsackProblem = KnapsackProblem.Solve(items, 14); Console.WriteLine(knapsackProblem); 👉 Наибольшая ценность при весе 11 кг: 7 = 3 + 1 Пишите варианты в комментариях. Решение будет сегодня вечером новым постом в канале. #Задача #Lvl2

  • 🖥 Упрощаем код с global using Если мы хотим использовать какое-нибудь пространство имён в файлах кода в проекте, то по умолчанию нам надо его подключать во все файлы, где мы планируем его использовать. ➡️ Допустим, у нас три файла с кодом Файл Car.cs с классом Car и интерфейсом IVehicle, всё в пространстве Vehicles: namespace Vehicles; public class Car : IVehicle { public string Brand { get; } public string Model { get; } public int Year { get; } public Car(string brand, string model, int year) { Brand = brand; Model = model; Year = year; } } public interface IVehicle { string Brand { get; } string Model { get; } int Year { get; } } Файл Person.cs, в пространстве Employees, с классом Person, который использует интерфейс IVehicle: using Vehicles; namespace Employees; public class Person { public string FullName { get; } public string City { get; } public IVehicle Vehicle { get; } public Person(string fullName, string city, IVehicle vehicle) { FullName = fullName; City = city; Vehicle = vehicle; } } Файл Program.cs, в котором используются классы Car и Person: using Employees; using Vehicles; List<Person> taxiFleet = new(); Car car = new("KIA", "Sorento", 2025); Person cabDriver = new("Antony John", "LA", car); taxiFleet.Add(cabDriver); И таких файлов, где надо подключать разные пространства имён, может быть множество. Это не очень удобно. Для этого предложена концепция глобальных пространств имён, который подключаются один раз сразу во все файлы кода в проекте. Для этого нам достаточно в одном файле прописать директиву: global using подключаемое_пространство_имён; ➡️ Глобальные пространства в отдельном файле Добавим в проект файл, который назовём Usings.cs и определим следующее содержимое: global using Employees; global using Vehicles; Теперь этот набор пространств имён будет автоматически подключаться во все файлы кода в проекте. А значит из файла Person.cs можно убрать строку: using Vehicles; А из файла Program.cs убрать строки: using Employees; using Vehicles; Данное преимущество позволяет делать код чище и лаконичнее + легче поддерживать проект. #Полезно

  • видео или голосовое, без подписи

C#Hive: Projects & Progress | Программирование — tgindex