Перехват исключения внутри IDisposable.Утилизировать

StackOverflow https://stackoverflow.com/questions/220234

  •  03-07-2019
  •  | 
  •  

Вопрос

В IDisposable.Dispose метод есть ли способ выяснить, генерируется ли исключение?

using (MyWrapper wrapper = new MyWrapper())
{
    throw new Exception("Bad error.");
}

Если в using заявление Я хочу знать об этом, когда IDisposable объект утилизирован.

Это было полезно?

Решение

Нет , в платформе .Net нет способа сделать это, вы не можете определить текущее исключение, которое создается в предложении finally.

См. эту запись в моем блоге для сравнения. с подобным шаблоном в Ruby, он подчеркивает пробелы, которые, я думаю, существуют с шаблоном IDisposable.

У Айенде есть хитрость, которая позволит вам обнаружить исключение, которое произошло , однако оно не сообщит вам, какое это было исключение.

Другие советы

Вы можете расширить IDisposable методом Complete и использовать шаблон следующим образом:

using (MyWrapper wrapper = new MyWrapper())
{
    throw new Exception("Bad error.");
    wrapper.Complete();
}

Если в выражении using возникнет исключение, Dispose не будет вызываться раньше AppDomain.CurrentDomain.FirstChanceException.

Если вы хотите узнать, какое именно исключение выдается, подпишитесь на событие ThreadLocal<Exception> и сохраните последнее выброшенное исключение в переменной TransactionScope.

Такой шаблон реализован в <=> классе.

невозможно зафиксировать исключение в методе Dispose().

Однако можно проверить Marshal.GetExceptionCode() в Dispose, чтобы определить, не произошло ли исключение, но я бы не стал на это полагаться.

Если вам не нужен класс и вы хотите просто захватить исключение, вы можете создать функцию, которая принимает лямбду, которая выполняется в блоке try / catch, что-то вроде этого:

HandleException(() => {
    throw new Exception("Bad error.");
});

public static void HandleException(Action code)
{
    try
    {
        if (code != null)
            code.Invoke();
    }
    catch
    {
        Console.WriteLine("Error handling");
        throw;
    }
}

В качестве примера, вы можете использовать метод, который автоматически выполняет Commit () или Rollback () транзакции и выполняет некоторую регистрацию. В этом случае вам не всегда нужен блок try / catch.

public static int? GetFerrariId()
{
    using (var connection = new SqlConnection("..."))
    {
        connection.Open();
        using (var transaction = connection.BeginTransaction())
        {
            return HandleTranaction(transaction, () =>
            {
                using (var command = connection.CreateCommand())
                {
                    command.Transaction = transaction;
                    command.CommandText = "SELECT CarID FROM Cars WHERE Brand = 'Ferrari'";
                    return (int?)command.ExecuteScalar();
                }
            });
        }
    }
}

public static T HandleTranaction<T>(IDbTransaction transaction, Func<T> code)
{
    try
    {
        var result = code != null ? code.Invoke() : default(T);
        transaction.Commit();
        return result;
    }
    catch
    {
        transaction.Rollback();
        throw;
    }
}

Джеймс, все, что wrapper может сделать, - это регистрировать свои собственные исключения. Вы не можете заставить потребителя <=> регистрировать свои собственные исключения. Это не то, что IDisposable для. IDisposable предназначен для полудетерминированного высвобождения ресурсов для объекта. Написание правильного кода IDisposable не тривиально.

Фактически, от потребителя класса даже не требуется вызывать метод утилизации ваших классов, и при этом они не обязаны использовать блок using, так что все это скорее сломается.

Если вы посмотрите на это с точки зрения класса-обертки, почему это должно заботиться о том, чтобы он присутствовал внутри блока using и было исключение? Какие знания это приносит? Есть ли угроза безопасности, когда сторонний код становится доступным к деталям исключений и трассировке стека? Что можно сделать <=>, если в расчете используется деление на ноль?

Единственный способ регистрировать исключения, независимо от IDisposable, - это try-catch, а затем перезапуск в catch.

try
{
    // code that may cause exceptions.
}
catch( Exception ex )
{
   LogExceptionSomewhere(ex);
   throw;
}
finally
{
    // CLR always tries to execute finally blocks
}
<Ч>

Вы упоминаете, что создаете внешний API. Вам нужно будет обернуть каждый вызов на открытой границе вашего API с помощью try-catch, чтобы записать, что исключение пришло из вашего кода.

Если вы пишете общедоступный API, то вам действительно следует прочитать рекомендации по разработке инфраструктуры : Соглашения, идиомы и шаблоны для многократно используемых библиотек .NET (Microsoft .NET Development Series) - 2-е издание .. 1-е издание .

<Ч>

Хотя я не защищаю их, я видел, как IDisposable используется для других интересных шаблонов:

<Ол>
  • Семантика автоматического отката транзакции. Класс транзакции откатит транзакцию при Dispose, если она еще не зафиксирована.
  • Временные блоки кода для регистрации. Во время создания объекта была записана временная метка, а при утилизации был рассчитан интервал времени и записано событие журнала.
  • * Эти шаблоны могут быть легко реализованы с помощью другого уровня косвенных и анонимных делегатов без необходимости перегружать семантику IDisposable. Важным примечанием является то, что ваша оболочка IDisposable бесполезна, если вы или член команды забудете ее правильно использовать.

    Вы можете сделать это, реализуя метод Dispose для " MyWrapper " учебный класс. В методе dispose вы можете проверить, есть ли исключение следующим образом

    public void Dispose()
    {
        bool ExceptionOccurred = Marshal.GetExceptionPointers() != IntPtr.Zero
                                 || Marshal.GetExceptionCode() != 0;
        if(ExceptionOccurred)
        {
            System.Diagnostics.Debug.WriteLine("We had an exception");
        }
    }
    

    Вместо синтаксического сахара выражения using почему бы просто не реализовать собственную логику для этого. Что-то вроде:

    try
    {
      MyWrapper wrapper = new MyWrapper();
    
    }
    catch (Exception e)
    {
      wrapper.CaughtException = true;
    }
    finally
    {
       if (wrapper != null)
       {
          wrapper.Dispose();
       }
    }
    

    Можно не только узнать, было ли сгенерировано исключение при удалении одноразового объекта, вы даже можете получить доступ к исключению, которое было сгенерировано внутри предложения finally, с помощью небольшого волшебства.Моя библиотека трассировки инструмента ApiChange использует этот метод для отслеживания исключений внутри инструкции using.Более подробную информацию о том, как это работает, можно найти здесь.

    Твой, Alois Kraus

    Теперь, в 2017 году, это общий способ сделать это, включая обработку отката для исключений.

        public static T WithinTransaction<T>(this IDbConnection cnn, Func<IDbTransaction, T> fn)
        {
            cnn.Open();
            using (var transaction = cnn.BeginTransaction())
            {
                try
                {
                    T res = fn(transaction);
                    transaction.Commit();
                    return res;
                }
                catch (Exception)
                {
                    transaction.Rollback();
                    throw;
                }
                finally
                {
                    cnn.Close();
                }
            }
        }
    

    и вы называете это так:

            cnn.WithinTransaction(
                transaction =>
                {
                    var affected = ..sqlcalls..(cnn, ...,  transaction);
                    return affected;
                });
    

    Это будет ловить исключения, сгенерированные напрямую или внутри метода dispose:

    try
    {
        using (MyWrapper wrapper = new MyWrapper())
        {
            throw new MyException("Bad error.");
        }
    }
    catch ( MyException myex ) {
        //deal with your exception
    }
    catch ( Exception ex ) {
        //any other exception thrown by either
        //MyWrapper..ctor() or MyWrapper.Dispose()
    }
    

    Но это зависит от них, использующих этот код - похоже, вы хотите, чтобы MyWrapper делал это вместо этого.

    Оператор using просто для того, чтобы всегда вызывать Dispose. Это действительно делает это:

    MyWrapper wrapper;
    try
    {
        wrapper = new MyWrapper();
    }
    finally {
        if( wrapper != null )
            wrapper.Dispose();
    }
    

    Звучит так, как ты хочешь:

    MyWrapper wrapper;
    try
    {
        wrapper = new MyWrapper();
    }
    finally {
        try{
            if( wrapper != null )
                wrapper.Dispose();
        }
        catch {
            //only errors thrown by disposal
        }
    }
    

    Я бы посоветовал разобраться с этим в вашей реализации Dispose - в любом случае вы должны решать любые вопросы во время удаления.

    Если вы связываете какой-то ресурс, где вам нужно, чтобы пользователи вашего API каким-либо образом его освободили, подумайте о методе Close(). Ваше распоряжение должно также вызывать его (если это еще не было), но пользователи вашего API могут также вызывать его сами, если им необходим более точный контроль.

    Если вы хотите остаться в .net, я бы предложил два подхода: написать "!" quot; try-catch-finally " обертка, которая будет принимать делегатов для разных частей или же писать " using-style " обертка, которая принимает метод для вызова вместе с одним или несколькими объектами IDisposable, которые должны быть удалены после его завершения.

    A " using-style " Оболочка может обрабатывать удаление в блоке try-catch и, если какие-либо исключения выбрасываются в удаление, либо обернуть их в исключение CleanupFailureException, которое будет содержать ошибки удаления, а также любое исключение, которое произошло в главном делегате, или добавить что-то к " Data " недвижимость с оригинальным исключением. Я бы предпочел оборачивать вещи в исключение CleanupFailureException, поскольку исключение, возникающее во время очистки, обычно указывает на гораздо большую проблему, чем проблема, возникающая при обработке основной строки; кроме того, CleanupFailureException может быть написано для включения нескольких вложенных исключений (если существует n объектов IDisposable, может быть n + 1 вложенных исключений: одно от основной линии и одно от каждой утилизации).

    A " try-catch-finally " Оболочка, написанная на vb.net, хотя и вызывается из C #, может включать некоторые функции, которые в противном случае недоступны в C #, в том числе возможность расширить ее до " try-filter-catch-fault-finally " блок, где " фильтр " код будет выполнен до того, как стек будет размотан из исключения, и определит, должно ли исключение быть перехвачено, " fault " Блок будет содержать код, который будет выполняться только в случае возникновения исключения, но на самом деле его не перехватит, а также " fault " и " наконец " блоки будут получать параметры, указывающие, какое исключение (если оно есть) возникло во время выполнения " try " и наличие " try " завершено успешно (заметьте, между прочим, что параметр исключения мог бы быть ненулевым, даже если основная строка завершена; чистый код C # не мог обнаружить такое условие, но обертка vb.net могла).

    В моем случае я хотел сделать это, чтобы регистрировать сбой микросервиса. У меня уже есть using для правильной очистки непосредственно перед закрытием экземпляра, но если это из-за исключения, я хочу понять, почему, и я ненавижу нет ответа.

    Вместо того, чтобы пытаться заставить его работать в Dispose(), возможно, сделайте делегата для работы, которую вам нужно сделать, а затем оберните туда свой захват исключений. Поэтому в моем логгере MyWrapper я добавляю метод, который принимает Action / Func:

     public void Start(Action<string, string, string> behavior)
         try{
            var string1 = "my queue message";
            var string2 = "some string message";
            var string3 = "some other string yet;"
            behaviour(string1, string2, string3);
         }
         catch(Exception e){
           Console.WriteLine(string.Format("Oops: {0}", e.Message))
         }
     }
    

    Для реализации:

    using (var wrapper = new MyWrapper())
      {
           wrapper.Start((string1, string2, string3) => 
           {
              Console.WriteLine(string1);
              Console.WriteLine(string2);
              Console.WriteLine(string3);
           }
      }
    

    В зависимости от того, что вам нужно сделать, это может быть слишком ограничительным, но это сработало для того, что мне нужно.

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top