Подтвердить что ты не робот

Можно ли расширить блок "использование" в С#?

Есть ли способ расширить блок 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)
{

}

Есть ли способ расширить С# таким образом?

4b9b3361

Ответ 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 должна иметь сжатый синтаксис для общего шаблона, для него не так полезно иметь сжатый синтаксис для редкого шаблона.