C#Hive: Projects & Progress | Программирование
Open in Telegram
Сообщество единомышленников C#: решаем задачи, учимся, развиваемся и общаемся вместе. Советы по работе на фрилансе, готовые проекты, код ревью, рекомендации и исследования. Вопросы/сотрудничество: @tel_phil9
Show more1 780
Subscribers
No data24 hours
-17 days
-1030 days
Posts Archive
Из примера выше, что будет выведено на экран?
Решение задачи к посту.
void CombSort(int[] values)
{
double factor = 1.247;
double step = values.Length - 1;
while (step >= 1)
{
for (int i = 0; i + step < values.Length; i++)
{
ref int v1 = ref values[i];
ref int v2 = ref values[(int)(i + step)];
if (v1 > v2) Swap(ref v1, ref v2);
}
step /= factor;
}
}
void Swap(ref int first, ref int second)
{
int temp = first;
first = second;
second = temp;
}
#Задача #Решение #Полезно🖥 Задача: сортировка расчёской (⭐️)
Написать метод, который принимает массив целочисленного типа и реализует алгоритм сортировки расчёской данного массива. Ниже приведены примеры.
int[] arr = { 35, 1, 73, 8998, 534, -124, 89, 572, 9, 61 };
CombSort(arr);
👉 [-124, 1, 9, 35, 61, 73, 89, 534, 572, 8998]
int[] arr = { 100, 90, 80, 70, 60, 50, 40, 30, 20, 1 };
CombSort(arr);
👉 [1, 20, 30, 40, 50, 60, 70, 80, 90, 100]
int[] arr = { 4025, 740, 8628, 8790, 18 };
CombSort(arr);
👉 [18, 740, 4025, 8628, 8790]
int[] arr = { -101, 101, -4, -88, 909, -1007 };
CombSort(arr);
👉 [-1007, -101, -88, -4, 101, 909]
Пишите варианты в комментариях. Решение будет сегодня вечером новым постом в канале.
#Задача #Lvl1🖥 Конструкция 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 #TryCatch
Из примера выше, что будет выведено на экран?
🖥 Await внутри конструкции lock
Сегодня рассмотрим вопрос с собеседования, где предоставлен следующий код:
object sync = new();
lock (sync)
{
await MethodAsync();
}
async Task MethodAsync()
{
// our code
// ...
}
Что будет с этим кодом и почему? Под спойлером ниже подробно разобран ответ, поэтому не спеши его смотреть, если интересно подумать и ответить самостоятельно.
Сперва отмечу, что данный код не скомпилируется. Можно подумать, что для команды разработчиков компилятора это слишком сложно/невозможно реализовать, но нет. Тот факт, что энтузиасты из stackoverflow пытались обойти это ограничение — прямое тому подтверждение. Скорее это невероятно плохая идея, потому это не допускается, чтобы избежать ошибок. Ожидание внутри блокировки — это рецепт создания взаимоблокировок (deadlocks).
➡️ Инверсия блокировок
Между моментом, когда await возвращает управление вызывающему потоку и моментом, когда метод возобновляет работу — выполняется произвольный код. Этот произвольный код может снимать блокировки, которые приводят к инверсии порядка блокировок и, следовательно, к взаимоблокировкам.
➡️ Выполнение в разных потоках
Более того, обычно вы снова возобновляете работу в потоке, который выполнил await, но это не обязательно. В сложных сценариях код может возобновиться в другом потоке, что не приведёт к разблокировке объекта sync исходным потоком.
#Полезно #Async #Await #СобеседованиеРешение задачи к посту.
void ShakerSort(int[] values)
{
int left = 0;
int right = values.Length - 1;
bool needContinue;
do
{
needContinue = false;
for (int i = left; i < right; i++)
{
if (values[i] > values[i + 1])
{
Swap(ref values[i], ref values[i + 1]);
needContinue = true;
}
}
right--;
for (int i = right; i > left; i--)
{
if (values[i] < values[i - 1])
{
Swap(ref values[i], ref values[i - 1]);
needContinue = true;
}
}
left++;
} while (needContinue);
}
void Swap(ref int first, ref int second)
{
int temp = first;
first = second;
second = temp;
}
#Задача #Решение #Полезно🖥 Задача: шейкерная сортировка (⭐️)
Написать метод, который принимает массив целочисленного типа и реализует алгоритм шейкерной сортировки данного массива. Ниже приведены примеры.
int[] arr = { 94, 1, 0, -100, 88, -3, 18 };
ShakerSort(arr);
👉 [-100, -3, 0, 1, 18, 88, 94]
int[] arr = { 5, 8, 7, 1, 14, 2, 13, 9, 11, 6, 10, 15, 12, 3, 4 };
ShakerSort(arr);
👉 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
int[] arr = { 2, 3, 4, 1 };
ShakerSort(arr);
👉 [1, 2, 3, 4]
int[] arr = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
ShakerSort(arr);
👉 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Пишите варианты в комментариях. Решение будет сегодня вечером новым постом в канале.
#Задача #Lvl1📈 Обогащаемся знаниями!
#Дайджест полезного материала за последнее время.
Задачи:
⏺Угадай число
⏺Сжатие данных без потерь (часть №1)
⏺Извлечение подчисла
⏺Деление слов на слоги
⏺Магическая матрица
⏺Пузырьковая сортировка
Опросы — что будет выведено на экран:
⏺№1
⏺№2
⏺№3
⏺№4
⏺№5
⏺№6
⏺№7
Полезное:
⏺Рефлексия на примерах
⏺Метод AsParallel от LINQ
⏺Рефлексия и атрибуты
⏺Частичные классы и методы
⏺Собственные операторы преобразования типов
⏺Разница между throw и throw ex при обработке исключений
⏺Разбор основ с делегатами
⏺Анонимные методы
⏺События
⏺Ковариантность и контравариантность делегатов
⏺Основные типы делегатов и замыкания
⏺Span и многомерные массивы
Самые важные хэштеги:
#Фриланс #Полезно #Задача #Опрос #LINQ #Проект
Из примера выше, в каком месте возникнет ошибка?
🖥 Span и многомерные массивы
Мы уже знаем, что из себя представляет структура Span, позволяющая эффективно работать с памятью. Однако явным образом структура принимает только одномерные массивы.
Если мы попробуем написать следующее для двухмерного массива, то результат, который мы ожидаем, получен не будет:
int[,] array = { };
Span<int> span1 = array; // Ошибка компиляции
Span<int> span2 = array.AsSpan(); // Ошибка компиляции
Однако один из перегруженных конструкторов структуры Span имеет возможность указания по указателю, благодаря чему мы можем написать следующий метод:
unsafe Span<int> AsSpan(int[,] matrix)
{
fixed (int* p = matrix) return new Span<int>(p, matrix.Length);
}
Таким подходом двухмерный массив, как и любой другой многомерный массив, сможет теперь управляться Span`ом. Используем его:
int[,] arr = { { 1, 2 }, { 3, 4 } };
Console.WriteLine($"{arr[0, 0]} {arr[0, 1]} {arr[1, 0]} {arr[1, 1]}");
Span<int> span = AsSpan(arr);
span[0] = 0;
span[3] = 0;
Console.WriteLine($"{arr[0, 0]} {arr[0, 1]} {arr[1, 0]} {arr[1, 1]}");
Вывод:
1 2 3 4 0 2 3 0Минус такого подхода в том, что Span работает с непрерывной областью памяти, а это может быть не очень удобно при попытке выделить определённые элементы, ведь они в Span будут предоставлены последовательно:
int[,] arr = { { 55, 12 }, { 97, 0 } };
Span<int> span = AsSpan(arr); // [55, 12, 97, 0]
Мы можем выделить промежуток элементов с 1 по 2 или с 2 по 4, но выделить только 1 и 4 элемент, без выделения дополнительной памяти, у нас не получится. Тем не менее, Span этим и прекрасен.
➡️ Выделение строки из матрицы
А чтобы из матрицы (двухмерного массива) выделить все элементы N-ой строки, мы можем написать нечто следующее:
unsafe Span<int> AsRowSpan(int[,] matrix, int indexRow)
{
fixed (int* p = matrix)
{
var span = new Span<int>(p, matrix.Length);
int columns = matrix.GetLength(1);
int start = indexRow * columns;
return span.Slice(start, columns);
}
}
Применим:
int[,] array2D = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Span<int> span = AsRowSpan(array2D, 1); // [4, 5, 6]
span.Fill(-1);
// Теперь array2D = [1, 2, 3, -1, -1, -1, 7, 8, 9]
Либо же использовать существующее решение, приводящее к аналогичному результату:
int[,] array2D = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Span<int> span = MemoryMarshal.CreateSpan(ref array2D[1, 0], array2D.GetLength(1));
#Полезно #Span #Array #Unsafe #PointersРешение задачи к посту.
void BubbleSort<T>(T[] arr, bool byDescending) where T : struct, IComparable<T>
{
int end = arr.Length;
int targetSort = byDescending ? -1 : 1;
while (end != 0)
{
end--;
for (int i = 0; i < end; i++)
{
if (arr[i].CompareTo(arr[i + 1]) == targetSort)
{
T temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
}
}
#Задача #Решение #Полезно🖥 Задача: пузырьковая сортировка (⭐️)
Написать обобщённый метод, который принимает массив любого значимого типа и булевое значение, отвечающее за сортировку по убыванию/возрастанию. Метод должен реализовать алгоритм пузырьковой сортировки исходного массива. Ниже приведены примеры.
int[] array = { 4, 1, 2, 6, 3, 5, 2 };
BubbleSort<int>(arr: array, byDescending: false);
👉 [1, 2, 2, 3, 4, 5, 6]
long[] array = { 55, 11, 41763609734, 63, 8, -336, 128, 95, 0 };
BubbleSort(array, true);
👉 [41763609734, 128, 95, 63, 55, 11, 8, 0, -336]
float[] array = { -1.23f, 3.14f, 9 };
BubbleSort(array, true);
👉 [9, 3,14, -1,23]
DateTime[] array = { new DateTime(2012, 12, 21), new DateTime(2024, 11, 7), DateTime.Parse("14.8.2021") };
BubbleSort(array, false);
👉 [21.12.2012 00:00:00, 14.08.2021 00:00:00, 07.11.2024 00:00:00]
Пишите варианты в комментариях. Решение будет сегодня вечером новым постом в канале.
#Задача #Lvl1🖥 Основные типы делегатов и замыкания
В .NET есть несколько встроенных делегатов, которые используются в различных ситуациях. И наиболее используемыми являются Action, Predicate и Func.
➡️ Action
Делегат представляет собой некоторое действие, которое ничего не возвращает, то есть в качестве возвращаемого типа имеет тип void:
public delegate void Action()
public delegate void Action<in T>(T obj)
Обобщённый делегат имеет ряд перегруженных версий. Каждая версия принимает разное число параметров: от Action<in T> до Action<in T1, in T2, ... in T16>. Таким образом можно передать до 16 значений в метод.
Как правило, этот делегат передаётся в качестве параметра метода и предусматривает вызов определённых действий в ответ на произошедшее действие:
DoOperation(10, 6, Add); // 10 + 6 = 16
DoOperation(10, 6, Multiply); // 10 * 6 = 60
void DoOperation(int a, int b, Action<int, int> op) => op(a, b);
void Add(int x, int y) => Console.WriteLine($"{x} + {y} = {x + y}");
void Multiply(int x, int y) => Console.WriteLine($"{x} * {y} = {x * y}");
➡️ Predicate
Делегат представляет собой некоторое условие, который принимает один параметр и возвращает значение типа bool:
delegate bool Predicate<in T>(T obj);
Как правило, используется для сравнения, сопоставления некоторого объекта T определённому условию. В качестве выходного результата возвращается значение true, если условие соблюдено, и false, если не соблюдено:
Predicate<int> isPositive = (int x) => x > 0;
Console.WriteLine(isPositive(20));
Console.WriteLine(isPositive(-20));
➡️ Func
Делегат представляет собой функцию, которая возвращает результат действия и может принимать параметры:
public delegate TResult Func<out TResult>()
public delegate TResult Func<in T, out TResult>(T arg)
Он также имеет различные формы: от Func<out T> до Func<in T1, in T2, ... in T16, out TResult>, т.е. может принимать до 16 параметров.
Данный делегат также часто используется в качестве параметра в методах:
int result = DoOperation(6, DoubleNumber);
Console.WriteLine(result); // 12
result = DoOperation(6, SquareNumber);
Console.WriteLine(result); // 36
int DoOperation(int n, Func<int, int> operation) => operation(n);
int DoubleNumber(int n) => 2 * n;
int SquareNumber(int n) => n * n;
➡️ Замыкания
Замыкание — это объект функции, который запоминает своё лексическое окружение даже в том случае, когда она выполняется вне своей области видимости.
Технически, замыкание включает:
⏺Внешняя функция, которая определяет некоторую область видимости и в которой определены некоторые переменные и параметры;
⏺Лексическое окружение — переменные и параметры, которые определены во внешней функции;
⏺Вложенная функция, которая использует переменные и параметры внешней функции.
Рассмотрим реализацию замыканий через локальные функции:
var fn = Outer(); // Возвращает метод Inner()
// Вызываем внутреннюю функцию Inner()
fn(); // 6
fn(); // 7
fn(); // 8
Action Outer() // Метод или внешняя функция
{
int x = 5; // Лексическое окружение — локальная переменная
return Inner; // Возвращаем локальную функцию
void Inner() // Локальная функция
{
x++; // Операции с лексическим окружением
Console.WriteLine(x);
}
}
Переменная fn и представляет собой замыкание, т.к. объединяет функцию и окружение, в котором функция была создана. И несмотря на то, что мы получили локальную функцию и можем её вызывать вне её метода, в котором она определена, она запомнила своё лексическое окружение и может к нему обращаться и изменять.
➡️ Применение параметров в замыкании
Кроме внешних переменных к лексическому окружению также относятся параметры окружающего метода:
var fn = Multiply(5);
Console.WriteLine(fn(5)); // 25
Console.WriteLine(fn(6)); // 30
Console.WriteLine(fn(7)); // 35
Func<int, int> Multiply(int n)
{
return Inner;
int Inner(int m) => n * m;
}
Как итог, при вызове метода Multiply определяется переменная fn, которая получает локальную функцию Inner и её лексическое окружение — значение параметра n (в нашем случае n = 5).
#Полезно #DelegateИз примера выше, что будет выведено на экран?
