Оценка 20 Оценить ![]() ![]() ![]() ![]() ![]() ![]()
|
Одной из новых возможностей языка C# 6.0 являются фильтры исключений.
Общая идея довольно простая: рядом с блоком catch появляется возможность задать некоторый предикат, который будет определять, будет ли вызван блок catch.
private
bool _shouldHandle = false;
publicvoid CrazySample()
{
try
{
SomeMethod();
}
catch(Exception e) if (e.message == "42" && _shouldHandle)
{
// Handle exception
}
} |
Данный вариант синтаксиса доступен в публичной версии VS2015, но он будет изменен в финальной версии языка C#. Вместо if будет использоваться ключевое слово when.
Фильтр исключений логически эквивалентен условию в блоке catch с последующим пробросом исключения в случае невыполнения условия. Но, в случае полноценных фильтров исключений уровня CLR, порядок исполнения будет иным.
Генерирование исключения в CLR происходит следующим образом:
Это значит, что порядок исполнения генерации и обработки исключений будет таким:
public
void Run()
{
try
{
MethodThatThrows();
}
catch(UnvalidOperationException) //if (true)
{ }
catch(ArgumentException e) if (CanHandle(e.message == "Msg", "AE"))
{
Console.WriteLine("Caught AE");
}
catch(Exception e) if (CanHandle(true, "E"))
{
Console.WriteLine("Caught Exception");
}
}
privatebool CanHandle(bool canHandle, string msg)
{
Console.WriteLine("Canhandle {0}? {1}", msg, canHandle);
return canHandle;
}
privatevoid MethodThatThrows();
{
try
{
trow new ArgumentException("Ooops!")
}
catch(Exception)
{
Console.WriteLine("MethodThatThrows.catch");
throw;
}
finally
{
Console.WriteLine("MethodThatThrows.finally");
}
} |
Фильтры исключения могут быть полезными в следующих случаях:
У меня ни разу не возникало необходимости в фильтрах исключения для генерации более точных дампов, но команды Roslyn и TypeScript этим пользуются.
Второй сценарий использования связан с тем, что коды ошибок иногда проникают в исключения. SqlException содержит код ошибки, что может приводить к их императивному анализу вместо использования разных блоков catch. Фильтры исключений здесь могут сильно помочь:
private
void ExecuteSql()
{
try
{
RunSqlServerCommand();
}
catch (SqlException sex) if (sex.Number == 547) // FK violation
{
HandleForeignKeyViolation();
}
catch (SqlException sex) if (sex.Number == 42) // PK violation
{
HandlePrimaryKeyViolation();
}
} |
CLR содержит особый блок обработки исключений под названием fault – аналог блока finally, но который вызывается лишь в случае исключения. Этот блок не может обработать исключение и по его завершению исключение обязательно пробрасывается дальше.
С помощью фильтров исключений можно добавить этого же поведения:
public
void Sample()
{
try
{
Throw();
}
// Should be first catch block!catch (Exception e) if (LogException(e)) { }
catch(ArgumentException)
{}
catch(Exception)
{
Console.WriteLine("Caught Exception!");
}
}
privatebool LogException(Exception e)
{
Console.WriteLine("Exception: " + e);
returnfalse;
} |
Первый блок catch(Exception) можно рассматривать аналогом блока fault!
В этом случае всегда будет вызываться метод LogException, после чего начнется стандартный поиск нужного блока исключения. Так, в случае генерации InvalidOperationException, оно будет вначале залогировано, а обработано блоком catch(Exception).
Пример с логированием часто приводится в качестве одного из сценариев использования фильтров исключений. Тот же Мэдс Торгесен использует его в статье “New Features in C# 6”. Использовать фильтры исключений для этих целей вполне нормально, но нужно не забывать о правильном порядке блоков catch: первым блоком должен идти catch с фильтром, всегда возвращающим false, ПОСЛЕ которого должны располагаться все остальные блоки catch.
Основная опасность фильтров исключений кроется в их природе. Поскольку фильтры вызываются до блоков finally, то они вызываются в момент, когда блокировки еще не отпущены, файлы не закрыты, транзакции не завершены и т.д. В большинстве случаев проблем не будет, но отпилить себе ногу все же можно.
Например, генерация исключения из блока lock может легко привести к дедлоку:
private
readonly
object _syncRoot = newobject();
privatevoid CrazyMethod()
{
lock(_syncRoot)
{
Console.WriteLine("Throwing from lock!");
thrownew Exception("Ooops!")
}
}
publicvoid Deadlock()
{
try
{
CrazyMethod();
}
catch(Exception e) if(CanHandle(e))
{
Console.WriteLine("Handled!");
}
} |
Если CanHandle попробует захватить блокировку хитрым образом, то мы получим взаимоблокировку:
private
bool CanHandle(Exception e)
{
Task.Run(() =>
{
Console.WriteLine("Trying to handle an exception!");
lock(_syncRoot)
{}
}).Wait();
returntrue;
} |
Мало шансов столкнуться с взаимоблокировкой в таком простом виде, но более сложные сценарии все же могут привести к проблемам.
В каждой второй статье о фильтрах исключений в C# 6.0 говорится, что эта возможность есть также в VB.NET и в F#. К VB.NET претензий нет, а вот в F# фильтров исключений нет. Точнее как, они есть, но их нет. :)
let willThrow() =
try
printfn "throwing..."
failwith "Oops!"
finally
printfn "finally"let check (ex: Exception) =
printfn "check"
truelet CheckFilters() =
try
willThrow()
with
| ex when check(ex) -> printfn "caught!"
() |
Если запустить этот код, то вывод на экран будет таким:
throwing... finally check Caught! |
Фильтры исключений в F# не используют фильтры исключений CLR – это обычное выражение сопоставления с образцом!
Оценка 20 Оценить ![]() ![]() ![]() ![]() ![]() ![]()
|