Question

Dans la méthode IDisposable.Dispose, existe-t-il un moyen de savoir si une exception est générée?

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

Si une exception est générée dans l'instruction using, je souhaite en savoir plus lorsque l'objet IDisposable est supprimé.

Était-ce utile?

La solution

Non , il n'existe aucun moyen de le faire dans le cadre .Net, vous ne pouvez pas déterminer quelle est l'exception en cours qui est générée dans une clause finally.

Consultez ce article sur mon blog pour faire une comparaison. avec un motif similaire dans Ruby, il met en évidence les lacunes que je pense exister avec le motif IDisposable.

Ayende a une astuce qui vous permettra de détecter une exception s'est produite , mais il ne vous dira pas de quelle exception il s'agissait.

Autres conseils

Vous pouvez étendre IDisposable avec la méthode Complete et utiliser un modèle comme celui-ci:

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

Si une exception est générée à l'intérieur de la using déclaration Dispose ne sera pas appelée avant AppDomain.CurrentDomain.FirstChanceException.

Si vous voulez savoir quelle exception exacte est levée, abonnez-vous à ThreadLocal<Exception> l'événement et stockez la dernière exception levée dans la TransactionScope variable.

Ce modèle est implémenté dans <=> la classe.

Il est impossible de capturer l'exception dans la Dispose() méthode.

Cependant, il est possible de vérifier Marshal.GetExceptionCode() dans Dispose pour détecter si une exception s'est produite, mais je ne me fierais pas à cela.

Si vous n'avez pas besoin d'une classe et souhaitez simplement capturer l'exception, vous pouvez créer une fonction qui accepte un lambda exécuté dans un bloc try / catch, comme ceci:

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

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

Par exemple, vous pouvez utiliser une méthode qui effectue automatiquement un Commit () ou un Rollback () d’une transaction et effectue une certaine journalisation. De cette façon, vous n’avez pas toujours besoin d’un bloc 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;
    }
}

James, tout ce que wrapper peut faire est d’enregistrer ses propres exceptions. Vous ne pouvez pas forcer le consommateur de <=> à enregistrer ses propres exceptions. Ce n'est pas ce que IDisposable est pour. IDisposable est destiné à la libération semi-déterministe de ressources pour un objet. Écrire du code identifiable correct n’est pas trivial.

En fait, le consommateur de la classe n'est même pas obligé d'appeler la méthode de mise à disposition de ses classes, pas plus qu'ils ne sont obligés d'utiliser un bloc using, donc tout tombe en panne.

Si vous le regardez du point de vue de la classe wrapper, pourquoi devrait-il se soucier qu’il soit présent dans un bloc using et qu’il existe une exception? Quelles connaissances cela apporte-t-il? Avoir un code tiers privé des détails des exceptions et du suivi de pile est-il un risque pour la sécurité? Que peut <=> faire s’il existe une division par zéro dans un calcul?

Le seul moyen de consigner les exceptions, qu’elles soient ou non identifiables, est d’essayer, puis de relancer la capture.

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

Vous dites que vous créez une API externe. Vous devrez encapsuler chaque appel à la limite publique de votre API avec try-catch afin de vous assurer que l'exception provient de votre code.

Si vous écrivez une API publique, vous devez absolument lire les consignes relatives à la conception de la structure. : Conventions, expressions et modèles pour les bibliothèques .NET réutilisables (série de développement Microsoft .NET) - 2e édition .. 1re édition .

Bien que je ne les préconise pas, j'ai déjà vu IDisposable utilisé pour d'autres motifs intéressants:

  1. Sémantique de transaction d'annulation automatique. La classe de transaction annulerait la transaction sur Dispose si elle n’était pas déjà validée.
  2. Blocs de code temporisés pour la journalisation. Au cours de la création de l'objet, un horodatage a été enregistré et, sur Dispose, TimeSpan a été calculé et un événement de journal a été écrit.

* Ces modèles peuvent être obtenus avec une autre couche de délégués indirectionnels et anonymes facilement et sans avoir à surcharger la sémantique IDisposable. La remarque importante est que votre emballage pouvant être utilisé est inutile si vous ou un membre de l’équipe oubliez de l’utiliser correctement.

Vous pouvez faire cela en implémentant la méthode Dispose pour & "MyWrapper &"; classe. Dans la méthode de disposition, vous pouvez vérifier s'il existe une exception comme suit

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

Au lieu du sucre syntaxique de l'instruction using, pourquoi ne pas simplement implémenter votre propre logique pour cela. Quelque chose comme:

try
{
  MyWrapper wrapper = new MyWrapper();

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

Il est non seulement possible de savoir si une exception a été lancée lorsqu’un objet jetable est disposé, vous pouvez même mettre la main sur l’exception qui a été jetée dans la clause finally avec un peu de magie. Ma bibliothèque de suivi de l'outil ApiChange utilise cette méthode pour suivre les exceptions dans une instruction using. Vous trouverez plus d'infos sur comment cela fonctionne ici .

vôtre,    Alois Kraus

En 2017, c’est la façon générique de le faire, y compris la gestion des restaurations d’exceptions.

    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();
            }
        }
    }

et vous l'appelez comme ceci:

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

Ceci interceptera les exceptions émises directement ou à l'intérieur de la méthode de disposition:

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()
}

Mais cela s'appuie sur eux en utilisant ce code - il semblerait que vous souhaitiez que MyWrapper le fasse à la place.

L'instruction using est juste pour s'assurer que Dispose est toujours appelé. C'est vraiment ça:

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

Cela ressemble à ce que vous voulez, c'est:

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

Je suggérerais de régler ce problème lors de la mise en œuvre de Dispose - vous devez de toute façon gérer les problèmes lors de l'élimination.

Si vous bloquez une ressource sur laquelle vous avez besoin d'utilisateurs de votre API pour la libérer, envisagez d'utiliser une méthode Close(). Votre disposition devrait également l'appeler (si ce n'est déjà fait), mais les utilisateurs de votre API peuvent également l'appeler eux-mêmes s'ils avaient besoin d'un contrôle plus précis.

Si vous souhaitez rester purement dans .net, deux méthodes que je suggérerais consisteraient à écrire un & "try-catch-finally &"; wrapper, qui accepterait des délégués pour les différentes parties ou rédigerait un & "using-style &"; wrapper, qui accepte l’invocation d’une méthode, ainsi qu’un ou plusieurs objets identifiables qu’il convient de supprimer une fois l’opération terminée.

Un "-using-style " wrapper peut gérer la mise au rebut dans un bloc try-catch et, si des exceptions sont générées, soit les envelopper dans une exception CleanupFailureException qui contiendrait les échecs de mise au rebut ainsi que toute exception survenue dans le délégué principal, ou bien ajouter quelque chose à " Data " propriété avec l'exception originale. Je préférerais que les choses soient emballées dans une CleanupFailureException, car une exception qui se produit pendant le nettoyage indiquera généralement un problème beaucoup plus important que celui rencontré dans le traitement de la ligne principale; En outre, une exception CleanupFailureException peut être écrite pour inclure plusieurs exceptions imbriquées (s’il existe des objets 'n' IDisposable, il peut y avoir n + 1 exceptions imbriquées: une de la ligne principale et une de chaque Dispose).

Un " essayer-attraper-enfin & "; Le wrapper écrit en vb.net, bien qu’appelable à partir de C #, pourrait inclure certaines fonctionnalités qui ne seraient pas disponibles autrement en C #, notamment la possibilité de l’étendre à un & "try-filter-catch-catch-fault-finally &"; block, où le " filtre " le code serait exécuté avant que la pile soit levée d'une exception et détermine si l'exception doit être interceptée, le & "défaut &"; block contiendrait du code qui ne fonctionnerait que si une exception se produisait, mais ne l'attraperait pas réellement, et le & "défaut &"; et " enfin " les blocs recevraient des paramètres indiquant à la fois quelle exception (s'il y en avait une) survenue lors de l'exécution de & "try &" et si le & "try &"; terminé avec succès (remarque, btw, qu'il serait possible que le paramètre d'exception ne soit pas nul, même si la ligne principale est terminée; le code C # pur ne pouvait pas détecter une telle condition, mais le wrapper vb.net le pouvait).

Dans mon cas, je voulais effectuer cette opération pour me connecter lorsqu'un microservice tombe en panne. J'ai déjà mis en place un using moyen de nettoyer correctement avant la fermeture d'une instance, mais si c'est à cause d'une exception, je veux voir pourquoi et je déteste non pour une réponse.

Au lieu d'essayer de le faire fonctionner dans Dispose(), définissez peut-être un délégué pour le travail que vous devez effectuer, puis encapsulez votre capture des exceptions. Donc, dans mon enregistreur MyWrapper, j'ajoute une méthode qui prend une 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))
     }
 }

Pour implémenter:

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

Selon ce que vous devez faire, cela peut être trop restrictif, mais cela a fonctionné pour ce dont j'avais besoin.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top