Можно ли расширить блок "использование" в С#?
Есть ли способ расширить блок using
на С# таким образом, чтобы делегат был вторым параметром рядом с объектом IDisposable и выполнялся каждый раз, когда исключение выбрасывается внутри этого блока using
?
Представьте, что у нас есть делегат, что-то вроде этого:
public delegate void ExceptionHandler(Exception ex);
И предположим, что у меня есть метод, который соответствует этому делегату, примерно так:
public void Log(Exception ex)
{
// Some logging stuff goes here
}
И я хочу сделать что-то вроде этого:
using(SqlConnection connection = new SqlConnection(""), Log)
{
}
Есть ли способ расширить С# таким образом?
Ответы
Ответ 1
A using
блок является сокращением для блока try finally
с вызовом Dispose
в finally
. Он не может быть расширен, чтобы быть чем-то большим. То, что вы хотите, это функциональность try catch finally
, поэтому почему бы не использовать именно это:
SqlConnection connection = new SqlConnection("");
try {
}
catch (Exception exc) {
Log(exc);
}
finally {
connection.Dispose();
}
Это связано со всеми преимуществами try catch finally
, например, для поиска нескольких типов исключений и фильтров исключений С# 6.0. Рассмотрим это:
SqlConnection connection = new SqlConnection("");
try {
}
catch (SqlException exc) when (exc.Number > 0) {
//Handle SQL error
}
catch (Exception exc) {
Log(exc);
}
finally {
connection.Dispose();
}
Если вы хотите повторно использовать стандартизованные блоки try catch finally
, вы можете использовать делегаты:
static class ErrorHandler {
public static ExecuteWithErrorHandling<T>(Func<T> createObject,
Action<Exception> exceptionHandler, Action<T> operation) where T : IDisposable {
T disposable = createObject();
try {
operation(disposable);
}
catch (Exception exc) {
exceptionHandler(exc);
}
finally {
disposable.Dispose();
}
}
}
Что вы можете назвать:
ErrorHandler.ExecuteWithErrorHandling(() => new SqlConnection(""), Log, connection => {
//Use connection here
});
Ответ 2
Вы не можете расширить оператор using
, но можете его обернуть в методе:
void DoStuff(Action action, ExceptionHandler log)
{
using(var connction = new SqlConnection(""))
{
try
{
action();
}
catch(Exception e)
{
log(e)
}
}
}
Ответ 3
Просто немного отступите от синтаксического сахара.
С
using(var obj = factory_or_constructor())
{
// Do Stuff
}
является сокращением общего шаблона
obj = factory_or_constructor();
try
{
// Do Stuff
}
finally
{
((IDisposable)obj)?.Dispose();
}
Тогда вы можете просто изменить его на:
try
{
// Do Stuff
}
catch(Exception ex)
{
Log(ex);
throw;
}
finally
{
((IDisposable)obj)?.Dispose();
}
Но тогда он действительно не предлагает намного больше по сравнению с более простым и понятным.
using(var obj = factory_or_constructor())
{
try
{
// Do Stuff
}
catch(Exception ex)
{
Log(ex);
throw;
}
}
Это не "расширение", но тогда, если точка using
должна иметь сжатый синтаксис для общего шаблона, для него не так полезно иметь сжатый синтаксис для редкого шаблона.