.NET Разработчик
6.55K subscribers
442 photos
3 videos
14 files
2.12K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День триста пятьдесят девятый. #NetInternals
Начинаю новую серию постов в стиле вопросы и ответы из книги Адама Фурманека «.NET Internals Cookbook».
От автора: "В этой серии я отвечаю на различные вопросы по .NET. Некоторые из них задают на собеседованиях, некоторые я вижу в Интернете, остальные придуманы. Цель - предоставить краткий ответ со ссылками на источники, если это необходимо. Это ни в коем случае не учебник по .NET, это просто набор полезных ответов, чтобы освежить ваши знания."

1. Что происходит, когда вы выбрасываете что-то, что не наследуется от System.Exception? Изменилось ли это со времён .NET 1?
Во-первых, у вас может возникнуть вопрос: «Как вообще это сделать?» Помимо C# существуют другие языки, работающие в CLR. Одним из них является C++/CLI, который является управляемой версией языка C++. В C++ вы можете выбрасывать что угодно – integer, string, byte и т.д. Если вы попытаетесь сделать это в C++/CLI, вы фактически выбросите нечто, что не наследует от System.Exception.

Выбрасываемый объект помещается в System.Runtime.CompilerServices.RuntimeWrappedException, поэтому вы всё равно сможете перехватить исключение с помощью обычного блока catch (Exception e) {}.

Однако до .NET 2 всё было иначе. Выброшенный объект оборачивался, поэтому вы не могли поймать его таким образом. Вам пришлось бы использовать нетипизированную версию блока catch в виде catch {}. По этой причине вы могли видеть код, подобный следующему:
try
{
//
}
catch(Exception e)
{
//
}
catch
{
//
}
После перехода на .NET 2 этот код перестанет компилироваться, потому что последний блок catch никогда не будет выполняться. Вы можете изменить код или восстановить старое поведение, используя атрибут RuntimeCompatibilityAttribute.

Источник: Adam Furmanek «.NET Internals Cookbook» - https://blog.adamfurmanek.pl/
День триста шестьдесят пятый. #NetInternals
2. Как проглотить ThreadAbortException?
Иногда в коде нужно остановить выполнение одного из потоков. Для этого можно использовать метод thread.Abort(). При вызове метода Abort в останавливаемом потоке выбрасывается исключение ThreadAbortException.
Вы можете легко поймать его с помощью блока исключений, но, если вы не сбросите его, оно будет автоматически проброшено выше по стеку.
var thread = new Thread(() => {
try { … }
catch (ThreadAbortException e) {

}
});

thread.Abort();
Если всё-таки нужно обработать ThreadAbort и выполнить еще какие-то действия в останавливаемом потоке, то можно использовать метод Thread.ResetAbort(). Он прекращает процесс остановки потока и исключение перестаёт прокидываться выше по стеку. Важно понимать, что метод thread.Abort() сам по себе ничего не гарантирует — код в останавливаемом потоке может препятствовать остановке.

Еще одна особенность thread.Abort() заключается в том, что он не сможет прервать код в том случае, если он находится в блоках catch и finally. Внутри кода фреймворка часто можно встретить методы, у которых блок try пустой, а вся логика находится внутри finally. Это делается как раз с той целью, чтобы этот код не мог быть прерван через ThreadAbortException.

Также вызов метода thread.Abort() дожидается выброса ThreadAbortException. Объединим эти два факта и получим, что метод thread.Abort() может заблокировать вызывающий поток:
var thread = new Thread(() =>
{
try { }
catch {
//ThreadAbortException не выбрасывается в catch
}
finally {
//ThreadAbortException не выбрасывается в finally
Thread.Sleep(-1);
}
});
thread.Start();

thread.Abort(); //Никогда не вернётся

В реальности с этим можно столкнуться при использовании конструкции using. Она разворачивается в try/finally, внутри finally вызывается метод Dispose. Он может быть сколь угодно сложным, содержать вызовы и обработчики событий, использовать блокировки. И если thread.Abort был вызван во время выполнения Dispose, то thread.Abort будет его ждать. Так мы получаем блокировку почти на пустом месте.

В .NET Core метод thread.Abort() выбрасывает PlatformNotSupportedException. И это очень хорошо, потому что мотивирует пользоваться не thread.Abort(), а неинвазивными методами остановки выполнения кода, например с помощью CancellationToken.

Источники:
- Adam Furmanek «.NET Internals Cookbook» -
https://blog.adamfurmanek.pl/
- Евгений Пешков «Особые исключения в .NET» -
https://youtu.be/WLSrYgMWif4
День триста семьдесят первый. #NetInternals
3. Можно ли использовать методы расширения с dynamic?
Нет. Код можно проверить здесь
using System;
public class Program {
public static void Main() {
int x = 5;
x.Extend();
dynamic y = x;
y.Extend();
}
}

static class Foo {
public static void Extend(this int x) {
Console.WriteLine(x);
}
}

Это потому, что методы расширения компилируются в статические. Код выше фактически компилируется в это:
IL_0004: call void Foo::Extend(int32)

В C# вы могли бы написать его как:
Foo.Extend(x);

Однако DLR (Динамическая Среда Выполнения) ищет методы объекта во время выполнения, поэтому вы не можете использовать расширения с динамическими переменными.

Источник: Adam Furmanek «.NET Internals Cookbook» - https://blog.adamfurmanek.pl/