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

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

前往频道在 Telegram

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

显示更多
1 780
订阅者
无数据24 小时
-17 天
-1030 天
帖子存档
🖥 ValueTask для максимальной производительности Разница между Task и ValueTask заключается в том, что Task — ссылочный тип,
🖥 ValueTask<T> для максимальной производительности Разница между Task<T> и ValueTask<T> заключается в том, что Task<T> — ссылочный тип, а ValueTask<T> — значимый тип. Эти типы представляют асинхронные операции, но имеют разные особенности и используются в разных случаях. Использование типа ValueTask<T> во многом аналогично применению Task<T>, за исключением некоторых различий в работе с памятью. ValueTask — структура, которая содержит большее количество полей. Поэтому применение ValueTask вместо Task приводит к копированию большего количества данных и соответственно создаёт некоторые дополнительные издержки. Преимуществом ValueTask перед Task является то, что данный тип позволяет избежать дополнительных выделений памяти в куче. Например, иногда требуется синхронно возвратить некоторое значение. Если асинхронная операция часто завершается синхронно (например, данные уже находятся в кэше), использование ValueTask<T> может избежать выделения памяти для Task, что положительно скажется на производительности, как в примере ниже:
public async ValueTask<string> GetDataAsync(string key)
{
    if (cache.TryGetValue(key, out string value))
    {
        // Завершается синхронно
        return value;
    }

    // Асинхронный запрос
    value = await dataSource.GetDataAsync(key);
    cache[key] = value;
    return value;
}
#Полезно #Async #Await

Дошкольные курсы программирования: #Юмор
Дошкольные курсы программирования: #Юмор

🖥 Из примера выше, что будет выведено на экран?
Anonymous voting

🖥 Консоль: читаемый ввод/вывод Консоль крайне удобная и полезная вещь в программировании, а значит нам нужно хорошо понимать её нюансы, как и нюансы любых других инструментов, работать с которыми нам приходится. Напишем следующий код:
string outRu, outEn;
outRu = "Русский вывод (RU). Ввод: ";
outEn = "English output (EN). Input: ";

Console.Write(outRu);
Console.ReadLine();

Console.Write(outEn);
Console.ReadLine();
Ожидаемый вывод из кода выше нам вполне очевиден, но может возникнуть ситуация, при которой можно увидеть нечто следующее:
??????? ????? (RU). ????: 10 десять
English output (EN). Input: 10 ten
Особая паника может накрыть в случаях, когда на нашем ПК всё работало красиво и без косяков, а после передачи решения заказчику он кидает нам скриншоты таких выводов и недоумевает — ничего же непонятно. Проблемы в этом даже нет, ибо всё решается настройкой кодировки. Ниже разберу два варианта решения. ➡️ Первый вариант В первом случае нам нужно изменить язык программ во всей системе. Порядок действий: 1. Через "Пуск" заходим в "Панель управления"; 2. Выбираем "Региональные стандарты"; 3. В открывшемся окне жмём на вкладку "Дополнительно"; 4. Кликаем кнопку "Изменить язык системы"; 5. Далее уже дело логики. Из списка выбираем "Русский (Россия)" и сохраняем результат. Однако, вероятнее всего, на нашем личном ПК всё это уже настроено, а поэтому такой подход будет более уместен в случаях, когда мы льём софт на чистый сервер. Возможно и такое, что эти настройки уже установлены, а вывод в консоль всё равно остаётся нечитаемым, либо же вообще с пустыми строками. Тогда нам нужно перейти ко второму варианту. ➡️ Второй вариант Здесь же решение заключается в настройке ввода/вывода внутри приложения. Для этого нам нужно поиграться со свойствами класса Console. В большинстве случаев, проблему решит следующий код:
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
Либо, если не помогло, то задать свойствам Encoding.Unicode. Всё методом проб и ошибок. Главное помнить, что задать настройки консоли нужно в самом начале, а значит полный код будет выглядеть так:
Console.InputEncoding = Encoding.Unicode;
Console.OutputEncoding = Encoding.Unicode;

string outRu, outEn;
outRu = "Русский вывод (RU). Ввод: ";
outEn = "English output (EN). Input: ";

Console.Write(outRu);
Console.ReadLine();

Console.Write(outEn);
Console.ReadLine();
#Полезно #Консоль

Решение задачи к посту.
void KeywordExtractor(string text)
{
    if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException();

    Dictionary<string, int> dict = new();
    var words = ToWords(text);

    foreach (string word in words)
    {
        if (string.IsNullOrWhiteSpace(word)) continue;

        int quantity = dict.ContainsKey(word) ? dict[word] + 1 : 1;
        dict[word] = quantity;
    }

    OutputTop10(dict);
}

string[] ToWords(string text)
{
    string words = text.ToLower();

    foreach (char sym in text) if (!char.IsLetter(sym)) words = words.Replace(sym, ' ');

    return words.Split(' ');
}

void OutputTop10(Dictionary<string, int> words)
{
    var top10 = words
        .OrderByDescending(x => x.Value)
        .Take(10)
        .ToArray();

    for (int i = 0; i < top10.Length; i++) Console.WriteLine($"{i + 1}. \"{top10[i].Key}\" — {top10[i].Value}");
}
#Задача #Решение #Полезно

🖥 Задача: ключевые слова (⭐️) Написать метод, который принимает текст, анализирует его, выделяя все слова, подсчитывая частоту их встречаемости и выводит список наиболее часто встречающихся слов (топ 10). Ниже приведены примеры.
string text = File.ReadAllText("Text.txt");
KeywordExtractor(text);

// Начинка Text.txt:
/*
У лукоморья дуб зелёный;
Златая цепь на дубе том:
И днём и ночью кот учёный
Всё ходит по цепи кругом;
Идёт направо — песнь заводит,
Налево — сказку говорит.
Там чудеса: там леший бродит,
Русалка на ветвях сидит;
Там на неведомых дорожках
Следы невиданных зверей;
Избушка там на курьих ножках
Стоит без окон, без дверей;
Там лес и дол видений полны;
Там о заре прихлынут волны
На брег песчаный и пустой,
И тридцать витязей прекрасных
Чредой из вод выходят ясных,
И с ними дядька их морской;
Там королевич мимоходом
Пленяет грозного царя;
Там в облаках перед народом
Через леса, через моря
Колдун несёт богатыря;
В темнице там царевна тужит,
А бурый волк ей верно служит;
Там ступа с Бабою Ягой
Идёт, бредёт сама собой,
Там царь Кащей над златом чахнет;
Там русский дух… там Русью пахнет!
И там я был, и мёд я пил;
У моря видел дуб зелёный;
Под ним сидел, и кот учёный
Свои мне сказки говорил.
*/
👉 1. "там" - 14 👉 2. "и" - 9 👉 3. "на" - 5 👉 4. "у" - 2 👉 5. "дуб" - 2 👉 6. "зелёный" - 2 👉 7. "кот" - 2 👉 8. "учёный" - 2 👉 9. "идёт" - 2 👉 10. "без" - 2
string text = File.ReadAllText("Text.txt");
KeywordExtractor(text);

// Начинка Text.txt:
/*
По мере расследования Тедди обнаруживает, что больница на острове скрывает большое количество тайн и ужасов. Маршал находит сбежавшую Рейчел Соландо, нашедшую пристанище в пещере. Подозрительным кажется и главный врач больницы — Джон Коули, с которого и начинается кульминация действия. Именно с подачи доктора Тедди осознаёт, что всё происходящее на острове — продукт воображения, а сам он является заключённым психиатрической больницы Эшклиффа в качестве пациента. 
*/
👉 1. "и" - 3 👉 2. "тедди" - 2 👉 3. "что" - 2 👉 4. "на" - 2 👉 5. "острове" - 2 👉 6. "в" - 2 👉 7. "больницы" - 2 👉 8. "с" - 2 👉 9. "по" - 1 👉 10. "мере" - 1
string text = File.ReadAllText("Text.txt");
KeywordExtractor(text);

// Начинка Text.txt:
/*
Теперь нам нужен класс, который и реализует валидацию нашего пароля. Я предпочитаю извлекать методы валидации в отдельные объекты, что позволяет более гибко тестировать фактические типы данных. Дополнительно класс-валидации мы можем использовать как временный объект с некоторой доп. инфой — оценка сложности пароля (слабый/средний/хороший) и т. п. —, без нужды вечного хранения такой информации.
*/
👉 1. "класс" - 2 👉 2. "и" - 2 👉 3. "пароля" - 2 👉 4. "валидации" - 2 👉 5. "теперь" - 1 👉 6. "нам" - 1 👉 7. "нужен" - 1 👉 8. "который" - 1 👉 9. "реализует" - 1 👉 10. "валидацию" - 1 Пишите варианты в комментариях. Решение будет сегодня вечером новым постом в канале. #Задача #Lvl1

Новичок: боится спугнуть клиента. Тем временем я: #Юмор

🖥 Из примера выше, что будет выведено на экран?
Anonymous voting

Всем тихих соседов. #Юмор

Решение задачи к посту.
void KaprekarConstant(int number)
{
    if (number < 100 || number > 9999) throw new ArgumentOutOfRangeException(nameof(number));

    int count = number < 1000 ? 3 : 4;
    int constant = count == 3 ? 495 : 6174;
    int[] digits = new int[count];

    while (number != constant)
    {
        ExtractDigits(number, digits);
        int ascending = AscendingValue(digits);
        int descending = DescendingValue(digits);
        number = descending - ascending;

        Console.WriteLine($"{descending} - {ascending} = {number}");
    }
}

void ExtractDigits(int from, int[] to)
{
    for (int i = 0; i < to.Length; i++)
    {
        from = Math.DivRem(from, 10, out int rem);
        to[i] = rem;
    }

    for (int i = 1; i < to.Length; i++) if (to[i - 1] != to[i]) return;
    throw new ArgumentException();
}

int AscendingValue(int[] from) => from.OrderBy(x => x).Aggregate((x, y) => x * 10 + y);

int DescendingValue(int[] from) => from.OrderByDescending(x => x).Aggregate((x, y) => x * 10 + y);
#Задача #Решение #Полезно

🖥 Задача: постоянная Капрекара (⭐️⭐️) Написать метод, который принимает число, реализует алгоритм нахождения постоянной Капрекара и выводит итерации на экран. Метод должен уметь работать с трёхзначными и четырёхзначными числами. Ниже приведены примеры.
KaprekarConstant(5621);
👉 6521 - 1256 = 5265 👉 6552 - 2556 = 3996 👉 9963 - 3699 = 6264 👉 6642 - 2466 = 4176 👉 7641 - 1467 = 6174
KaprekarConstant(796);
👉 976 - 679 = 297 👉 972 - 279 = 693 👉 963 - 369 = 594 👉 954 - 459 = 495
KaprekarConstant(3412);
👉 4321 - 1234 = 3087 👉 8730 - 378 = 8352 👉 8532 - 2358 = 6174
KaprekarConstant(6389);
👉 9863 - 3689 = 6174
KaprekarConstant(123);
👉 321 - 123 = 198 👉 981 - 189 = 792 👉 972 - 279 = 693 👉 963 - 369 = 594 👉 954 - 459 = 495 Пишите варианты в комментариях. Решение будет сегодня вечером новым постом в канале. #Задача #Lvl2

🖥 Валидация объектов Валидация — это процесс проверки данных различных типов по критериям корректности и полезности для конкретного применения. Смысл валидации в том, что мы совершаем проверку типа до его использования в дальнейшем. Она позволяет исключить поступление на вход заведомо ошибочных, неполных или неточных данных, которые могут привести к ошибочным результатам работы, утрате данных и сбоям в работе систем. Фреймворк .NET предлагает удобный функционал в виде атрибутов из пространства имён System.ComponentModel.DataAnnotations. Я же люблю ручную самовалидацию, что мы и разберём ниже. ➡️ Разбор на примере Рассмотрим крайне популярный процесс валидации на сегодняшний день — это корректность создаваемого пароля. Сперва напишем интерфейс для валидации объектов. Я предпочитаю использовать сразу две версии, классическую и с обобщением, что расширит его гибкость:
public interface IValidatable
{
    bool IsValid(object context);
}

public interface IValidatable<Tin, TOut> : IValidatable
{
    TOut Validate(Tin context);
}
Теперь вспомним о современных требованиях к паролю. По классике — это 8+ символов, минимум 1 цифра, минимум 1 заглавная буква и минимум 1 специальный символ. В связи с этим мы можем реализовать структуру с обязательными требованиями к паролю:
public struct PasswordRequirements
{
    public int MinLength { get; }
    public int MinUppercase { get; }
    public int MinDigits { get; }
    public int MinSpecialChars { get; }

    public PasswordRequirements() : this(8, 1, 1, 1) { }
    public PasswordRequirements(int minLength, int minUppercase, int minDigits, int minSpecialChars)
    {
        MinLength = minLength;
        MinUppercase = minUppercase;
        MinDigits = minDigits;
        MinSpecialChars = minSpecialChars;
    }
}
Теперь нам нужен класс, который и реализует валидацию нашего пароля. Я предпочитаю извлекать методы валидации в отдельные объекты, что позволяет более гибко тестировать фактические типы данных. Дополнительно класс-валидации мы можем использовать как временный объект с некоторой доп. инфой — оценка сложности пароля (слабый/средний/хороший) и т. п. —, без нужды вечного хранения такой информации. Напишем же такой класс:
public class PasswordValidation : IValidatable<string, bool>
{
    public static PasswordRequirements Requirements { get; }
    // Здесь можно добавить свойство
    // оценки сложности пароля

    static PasswordValidation() => Requirements = new();
    public PasswordValidation() { }

    public bool IsValid(object password) => Validate(password?.ToString());
    public bool Validate(string password)
    {
        if (string.IsNullOrWhiteSpace(password) || password.Length < Requirements.MinLength) return false;
        
        int uppercases = 0, digits = 0, specialChars = 0;
        foreach (char c in password)
        {
            if (char.IsUpper(c)) uppercases++;
            else if (char.IsDigit(c)) digits++;
            else if (!char.IsLetterOrDigit(c)) specialChars++;
        }

        return uppercases >= Requirements.MinUppercase && digits >= Requirements.MinDigits && specialChars >= Requirements.MinSpecialChars;
    }
}
Поскольку требования к паролю у нас для всех юзеров одинаковы свойство Requirements у меня статическое. Финалим наш пример фактическим типом пароля:
public class Password
{
    public string Hash { get; }

    static Password() => validator = new();
    public Password(string value)
    {
        if (!validator.Validate(value)) throw new FormatException();

        // Hash = ComputeMd5Hash(value);
        // ...
    }

    private static readonly PasswordValidation validator;
}
Таким образом пароль будет проверяться сразу при инициализации и выбрасывать исключение при несоответствии требований:
Password p1 = new("qwerty"); // Ошибка
Password p2 = new("123qwerty"); // Ошибка
Password p3 = new("Qwerty123@"); // Успех
Валидацию пароля можно проверять и до инициализации фактического класса, ибо классы независимы друг от друга — это одна из гибкостей, о которой я писал выше. #Полезно #Пароль #Generics

🖥 Из примера выше, что более вероятно будет выведено на экран?
Anonymous voting

🖥 Выражение stackalloc Ранее я уже немного писал о stackalloc, однако сейчас хочу разобрать более детально этот вопрос. Ключевое слово позволяет выделить память под массив в стеке. Смысл выделения памяти в стеке — это повышение быстродействия кода. Выделенный стеком блок памяти автоматически удаляется при выходе из этого метода, поэтому он не подлежит сборке мусора. ➡️ Убедимся в этом Конструкторы ссылочных типов, используя оператор new, выделяют память в куче, а значит мы можем убедиться в том, что stackalloc не занимает память в куче. Напишем класс:
[MemoryDiagnoser]
public class MemoryBenchmark
{
    public MemoryBenchmark() { }

    [Benchmark]
    public void HeapArray()
    {
        decimal[] arr = new decimal[100];
        
        for (int i = 0; i < 100; i++) arr[i] = 123;
    }

    [Benchmark]
    public unsafe void StackArray()
    {
        decimal* arr = stackalloc decimal[100];
        
        for (int i = 0; i < 100; i++) arr[i] = 123;
    }
}
Метод HeapArray создаёт массив в привычном нам виде, а метод StackArray создаёт массив через аллокацию памяти в стеке. Для бенчмарка мы помечаем класс и методы соответствующими атрибутами. Специально для примера берём самый объёмный тип decimal, который занимает 16 байт и 100 его элементов. Из этого следует, что мы ожидаем ~1600 занятых байтов в памяти (16 * 100). Запустим тест:
BenchmarkRunner.Run<MemoryBenchmark>();
Дождавшись результатов, я получил следующий отчёт:
| Method     | Mean     | Error   | StdDev   | Gen0   | Allocated |
|----------- |---------:|--------:|---------:|-------:|----------:|
| HeapArray  | 322.1 ns | 6.39 ns | 12.00 ns | 0.7763 |    1624 B |
| StackArray | 228.7 ns | 3.15 ns |  2.95 ns |      - |         - |
Столбец "Allocated" сообщает, что метод HeapArray занял 1624 байта, что мы и ожидали. Метод же StackArray ни аллоцировал и байта, что свидетельствует поставленной задаче. А теперь посмотрим на столбец "Mean": 322.1 наносекунд против 228.7 — разница в скорости заметная. ➡️ Некоторая особенность При выделении слишком большого объёма памяти в стеке возникает исключение StackOverflowException. Рекомендуется ограничить объём памяти, выделяемый под стек, например, как в следующем коде:
int length = 1000;
Span<byte> buffer = length <= 1024 ? stackalloc byte[length] : new byte[length];
Поскольку объём доступной памяти на стеке зависит от среды, в которой выполняется код, при определении фактического предельного значения следует использовать консервативное значение. ➡️ Проверим очистку стека Хоть это и ни к чему, но давайте убедимся в очистке стека после выхода из метода. Напишем и запустим следующий код:
var t = new Test();
t.Print();

public unsafe class Test
{
    public Test()
    {
        int* ptr = stackalloc int[length];
        pointer = ptr;

        for (int i = 0; i < length; i++) ptr[i] = i + 1;

        Print();
    }

    private const int length = 5;
    private int* pointer;

    public void Print()
    {
        for (int i = 0; i < length; i++) Console.WriteLine(pointer[i]);
        Console.WriteLine("------------");
    }
}
Вывод:
1
2
3
4
5
------------
1
0
2100389455
2046
5
------------
При первом выводе массива на экран всё работает как и ожидается, ибо вызов метода Print происходит внутри конструктора, где была выделена память. После выхода из конструктора и вызова метода Print во второй раз блок памяти уже заполнен иначе, а значит стек уже не хранит наш массив и дальнейшая попытка работы с ним небезопасна. #Полезно #Array #Unsafe #Pointers #Benchmark