tgindex

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

описание

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

1 636
подписчиков
Охват к подписчикам
44,6%
ERR
Реакции к просмотрам
0,41%
179 на 50 постов
Пересылки к просмотрам
0,98%
425
Постов в день
0,0
всего 50

Где отзываются чаще

доля реакций к просмотрам
  • 4 мая 2025 г.🖥 Фильтр Блума Представим, что мы крупный сайт — какой-нибудь маркетплейс — и нам нужно определять количество уникальных посетителей. Как мы это сделаем? Вполне логичным решением будет сохранение нового IP-адреса из потока сетевого трафика в список, а общее количество элементов такого списка и будет сообщать нам количество уникальных посетителей. Вопреки такому решению, хранение всех этих данных (IP`шки) для нас может быть лишним. Мы не будем их использовать в дальнейшем, но каждый хранимый объект в памяти будет непременно "раздувать" его общее заполнение. В оптимизации такой задачи нам и поможет фильтр Блума, который мы сегодня рассмотрим на примере поставленной задачи. ➡️ Что это и как он работает Фильтр Блума проверяет принадлежность конкретного элемента к множеству данных без необходимости его хранения. Он использует массив битов (0 — false, 1 — true), а также несколько хеш-функций. Когда новый объект попадает в базу, его хеш-коды вычисляются, заполняя соответствующие биты массива в значении 1. Например, если хеш-функция возвращает число 50, то 50 бит (он же индекс) массива устанавливается в 1. Так происходит добавление IP-адреса в фильтр. Если при получении нового IP-адреса все нужные биты уже установлены в значение 1, то это значит, что он уже встречался ранее. Но важно помнить, что фильтр Блума не даёт 100% гарантии, что IP-адрес был уже замечен. Всегда есть небольшой риск ложноположительного результата. Эта вероятность зависит от размера массива битов, количества хеш-функций и количества уникальных IP-адресов. Фильтр Блума экономит память системы, особенно при работе с большими множествами, ускоряя операции добавления/проверки. Он подходит для задач проверки принадлежности, особенно когда требуется скорость при работе системы в условиях ограниченной памяти. Несмотря на небольшой процент ложноположительных результатов, он широко используется в разных приложениях. ➡️ Снижение вероятности коллизий Коллизии возникают, когда несколько различных элементов имеют одинаковые хеш-коды, что может привести к ложноположительным результатам при проверке. Стратегии снижения вероятности коллизий: ⏺Увеличение размера массива битов; ⏺Увеличение количества хеш-функций; ⏺Выбор качественных хеш-функций. ➡️ Простая реализация public class BloomFilter<T> { public BloomFilter(int capacity, int hashFunctionCount) { if (capacity < 1) throw new ArgumentOutOfRangeException(nameof(capacity), "Размер должен быть положительным"); if (hashFunctionCount < 1) throw new ArgumentOutOfRangeException(nameof(hashFunctionCount), "Количество хеш-функций должно быть положительным"); bitArray = new(capacity); hashCount = hashFunctionCount; } private readonly BitArray bitArray; private readonly int hashCount; public void Add(T item) { foreach (int hash in GetHashValues(item)) { bitArray[hash] = true; } } public bool Contains(T item) { foreach (int hash in GetHashValues(item)) { if (!bitArray[hash]) return false; } return true; } public void Clear() => bitArray.SetAll(false); private IEnumerable<int> GetHashValues(T item) { for (int i = 0; i < hashCount; i++) { int seed = i * 31; int hash = (item?.GetHashCode() ?? 0) ^ seed; yield return Math.Abs(hash) % bitArray.Length; } } } Проверим: var filter = new BloomFilter<string>(1000, 5); var apple = "apple"; var banana = "banana"; filter.Add(apple); filter.Add(banana); Check(apple); Check(banana); Check("date"); Check("grape"); filter.Clear(); Check(apple); Check(banana); void Check(string item) => Console.WriteLine(filter.Contains(item)); Вывод: True True False False False False Отмечу, что реализация сильно простая. Рекомендую заменять GetHashCode на хеш-функцию MurmurHash2, MD5 или др. Ставьте реакции на пост, если хотите посмотреть на лучшую реализацию — напишу в комментариях. #Полезно #Хеширование #GetHashCode #Array2,72%
  • 25 сент. 2025 г.#Юмор1,37%
  • 28 авг. 2025 г.🖥 Упрощаем код с 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; Данное преимущество позволяет делать код чище и лаконичнее + легче поддерживать проект. #Полезно1,30%
  • 21 авг. 2024 г.без подписи1,09%
  • 17 февр. 2025 г.без подписи0,97%
  • 26 авг. 2025 г.🖥 Из примера выше, что будет выведено на экран?0,82%
  • 25 нояб. 2024 г.🖥 Await внутри конструкции lock Сегодня рассмотрим вопрос с собеседования, где предоставлен следующий код: object sync = new(); lock (sync) { await MethodAsync(); } async Task MethodAsync() { // our code // ... } Что будет с этим кодом и почему? Под спойлером ниже подробно разобран ответ, поэтому не спеши его смотреть, если интересно подумать и ответить самостоятельно. Сперва отмечу, что данный код не скомпилируется. Можно подумать, что для команды разработчиков компилятора это слишком сложно/невозможно реализовать, но нет. Тот факт, что энтузиасты из stackoverflow пытались обойти это ограничение — прямое тому подтверждение. Скорее это невероятно плохая идея, потому это не допускается, чтобы избежать ошибок. Ожидание внутри блокировки — это рецепт создания взаимоблокировок (deadlocks). ➡️ Инверсия блокировок Между моментом, когда await возвращает управление вызывающему потоку и моментом, когда метод возобновляет работу — выполняется произвольный код. Этот произвольный код может снимать блокировки, которые приводят к инверсии порядка блокировок и, следовательно, к взаимоблокировкам. ➡️ Выполнение в разных потоках Более того, обычно вы снова возобновляете работу в потоке, который выполнил await, но это не обязательно. В сложных сценариях код может возобновиться в другом потоке, что не приведёт к разблокировке объекта sync исходным потоком. #Полезно #Async #Await #Собеседование0,78%
  • 29 нояб. 2024 г.🖥 Конструкция try..catch..finally Глядя на результаты последнего опроса, становится ясно, что не каждый до конца понимает работу конструкции try..catch..finally. Ввиду этого ниже мы разберём детальнее эту особенность языка C#. Иногда при выполнении программы возникают ошибки, которые трудно предусмотреть или предвидеть, а иногда вовсе невозможно. Например, при передачи файла по сети может неожиданно оборваться сетевое подключение. такие ситуации называются исключениями. Для обработки таких ситуаций предназначена конструкция try..catch..finally: try { } catch { } finally { } При использовании данной конструкции вначале выполняются все инструкции в блоке try. Если в этом блоке не возникло исключений, то после его выполнения начинает выполняться блок finally. И затем конструкция try..catch..finally завершает свою работу. Если же в блоке try вдруг возникает исключение, то обычный порядок выполнения останавливается, и среда CLR начинает искать блок catch, который может обработать данное исключение. Если нужный блок catch найден, то он выполняется, и после его завершения выполняется блок finally. ➡️ Разбор на примере int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); Console.WriteLine("Конец программы"); Здесь происходит деление числа на 0, что приведёт к генерации исключения. И при запуске приложения в режиме отладки мы увидим в Visual Studio окошко, которое информирует об исключении. Единственное, что нам останется — это завершить выполнение программы. Чтобы избежать подобного аварийного завершения программы, следует использовать для обработки исключений конструкцию try..catch..finally. Так, перепишем пример следующим образом: try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); } finally { Console.WriteLine("Блок finally"); } Console.WriteLine("Конец программы"); Здесь также возникнет исключение в блоке try, по той же причине. И дойдя до строки деления на ноль выполнение программы остановится. CLR найдёт блок catch и передаст управление этому блоку. После блока catch будет выполняться блок finally: Возникло исключение! Блок finally Конец программы Таким образом, программа как и прежде не будет выполнять деление на ноль и, соответственно, не будет выводить результат этого деления, но теперь она не будет аварийно завершаться, а исключение будет обрабатываться в блоке catch. Следует отметить, что в этой конструкции обязателен блок try. При наличии блока catch мы можем опустить блок finally. И, наоборот, при наличии блока finally мы можем опустить блок catch и не обрабатывать исключение. ➡️ Блок finally Именно из-за этого блока бОльшая часть ответов в последнем опросе является ошибочным. Особенности блока: ⏺Выполняется в любом случае (при успешном выполнении try; при выбрасывании исключения; при передачи управления по break или return); ⏺Блок необязателен; ⏺Если есть finally, блоки catch необязательны; ⏺Выполняется последним, после блока try и всех выполняемых блоков catch. Если говорить кратко, то данный блок выполняется всегда перед выходом из всей конструкции try..catch..finally. #Полезно #Exception #TryCatch0,74%
  • 1 мар. 2025 г.без подписи0,72%
  • 16 янв. 2025 г.без подписи0,71%
  • 30 июн. 2024 г.🖥 Рефакторинг: извлечение интерфейса Продолжаем разбор. Данная техника позволяет разделить определение операции от их реализации, улучшая при этом модульность, упрощая тестирование и увеличивая гибкость кода. ➡️ Практический пример Представим, что у нас есть система обработки платежей с классом, который обрабатывает платежи через различные платёжные системы: public class PaymentProcessor { public void ProcessCreditCard(string cardNumber, decimal amount) { // Логика обработки платежей кредитной картой } public void ProcessPayPal(string email, decimal amount) { // Логика обработки платежей через PayPal } // Другие методы обработки платежей } Здесь класс прямо реализует методы для каждого типа платежа, однако это может стать проблематичным, если мы захотим добавить поддержку новых платёжных систем без изменения существующего класса. Извлечём интерфейс IPaymentProcessor для определения общего контракта платёжной системы и реализуем его для каждого класса платёжной системы отдельно: public interface IPaymentProcessor { void ProcessPayment(string accountIdentifier, decimal amount); } public class CreditCardProcessor : IPaymentProcessor { public void ProcessPayment(string cardNumber, decimal amount) { // Логика обработки платежей кредитной картой } } public class PayPalProcessor : IPaymentProcessor { public void ProcessPayment(string email, decimal amount) { // Логика обработки платежей через PayPal } } // Другие классы обработки платежей, реализующие IPaymentProcessor Теперь вместо прямого использования класса PaymentProcessor мы можем программировать на уровне интерфейсов, используя интерфейс IPaymentProcessor. Это позволяет легко добавлять новые способы обработки платежей не затрагивая при этом основной код системы. Эта техника особенно полезна при наличии нескольких классов, которые должны предоставлять единый набор функций, либо при обеспечении гибкости для будущих расширений. Учимся писать красиво! 👍 #Полезно #Рефакторинг0,66%
  • 16 июл. 2024 г.🖥 Применение хеш-функции SHA-256 к строке Хеш-функция, по сути, помогает превращать "в фарш" наши входные данные. Мы слышим это постоянно, однако не всегда понимаем «зачем?», «где?», а главное «как?». Зачастую, хеширование применяется при сохранении паролей, при построении уникальных идентификаторов, при создании электронной подписи и т.д. Увидев такую задачу, у неопытного разработчика часто возникает чувство тревоги, мол «ну не справлюсь я с этим». Ниже мы разберём практический пример (буквально из моих задач) и убедимся, что это довольно интересная вещь. ➡️ Пример Нашим примером будет электронная подпись запроса в Т-Банк при работе с платежами (к слову, хеширование встречается постоянно в подобных вопросах). Ознакомившись с документацией, становится ясно, что полученный хеш — это токен. Он же хранит в себе само тело запроса (корневую структуру JSON) со своим алгоритмом. Нам предлагают следующую структуру в качестве примера: { "TerminalKey": "MerchantTerminalKey", "Amount": 19200, "OrderId": "21090", "Description": "Подарочная карта на 1000 рублей" } Чтобы зашифровать данные такого запроса нужно выполнить следующий алгоритм: 1. Собрать массив передаваемых данных в виде пар ключ-значение; [{"TerminalKey": "MerchantTerminalKey"},{"Amount": "19200"},{"OrderId": "21090"},{"Description": "Подарочная карта на 1000 рублей"}] 2. Добавить в массив пару Password-Значение_Пароля. Пароль находится в ЛК; [{"TerminalKey": "MerchantTerminalKey"},{"Amount": "19200"},{"OrderId": "21090"},{"Description": "Подарочная карта на 1000 рублей"},{"Password": "usaf8fw8fsw21g"}] 3. Отсортировать массив по алфавиту по ключу; [{"Amount": "19200"},{"Description": "Подарочная карта на 1000 рублей"},{"OrderId": "21090"},{"Password": "usaf8fw8fsw21g"},{"TerminalKey": "MerchantTerminalKey"}] 4. Конкатенировать только значения пар в одну строку; "19200Подарочная карта на 1000 рублей21090usaf8fw8fsw21gMerchantTerminalKey" 5. Применить к строке хеш-функцию SHA-256 (с поддержкой UTF-8); "0024a00af7c350a3a67ca168ce06502aa72772456662e38696d48b56ee9c97d9" 6. Добавить получившийся результат в значение параметра Token в тело запроса, приложенное в качестве примера выше, и отправить запрос. Теперь решим эту задачу с помощью следующего класса: internal class Signature { internal string Token { get; private set; } internal Signature() { } internal void ComputeToken(KeyValuePair<string, string>[] pairs) { var sortedPairs = SortArrayByKey(pairs); string values = ConcatValuesInline(sortedPairs); Token = CalculateSha256Hash(values); } private IOrderedEnumerable<KeyValuePair<string, string>> SortArrayByKey(KeyValuePair<string, string>[] pairs) => pairs.OrderBy(x => x.Key); private string ConcatValuesInline(IOrderedEnumerable<KeyValuePair<string, string>> pairs) => string.Concat(pairs.Select(x => x.Value)); private string CalculateSha256Hash(string text) { byte[] bytes = Encoding.UTF8.GetBytes(text); using (SHA256Managed sha256 = new()) { byte[] hashBytes = sha256.ComputeHash(bytes); StringBuilder sb = new StringBuilder(); for (int i = 0; i < hashBytes.Length; i++) sb.Append(hashBytes[i].ToString("x2")); return sb.ToString(); } } } Используем класс и получим вывод в консоль того же токена, что и в примере: var pairs = new KeyValuePair<string, string>[] { new KeyValuePair<string, string>("TerminalKey", "MerchantTerminalKey"), new KeyValuePair<string, string>("Amount", "19200"), new KeyValuePair<string, string>("OrderId", "21090"), new KeyValuePair<string, string>("Description", "Подарочная карта на 1000 рублей"), new KeyValuePair<string, string>("Password", "usaf8fw8fsw21g") }; Signature signature = new(); signature.ComputeToken(pairs); Console.WriteLine(signature.Token); Таким вот интересным способом мы «лицензируем» свой запрос и получаем токен. Как видим, не так уж это и сложно, если внимательно читать инструкции и пытаться разобраться на примерах. #Полезно #SHA256 #Хеширование0,66%