web-dev-qa-db-fra.com

C # using error catch catch

Je ne fais que regarder la déclaration using, j'ai toujours su ce qu'elle fait mais jusqu'à présent je n'ai pas essayé de l'utiliser, j'ai mis au point le code ci-dessous:

 using (SqlCommand cmd = 
     new SqlCommand(reportDataSource, 
         new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)))
 {
     cmd.CommandType = CommandType.StoredProcedure;
     cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
     cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
     cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
     cmd.Connection.Open();

     DataSet dset = new DataSet();
     new SqlDataAdapter(cmd).Fill(dset);
     this.gridDataSource.DataSource = dset.Tables[0];
 }

Cela semble fonctionner, mais y a-t-il un intérêt à cela puisque, autant que je sache, il me faudrait tout de même inclure cela dans un bloc catch catch pour intercepter des erreurs imprévues, par exemple. serveur SQL vers le bas. Est-ce que je manque quelque chose?

Pour autant que je sache actuellement, cela ne fait que m'empêcher de fermer et de disposer de cmd, mais il y aura plus de lignes de code en raison de la capture d'essai toujours nécessaire.

28
PeteT

Ce code doit être comme suit pour garantir la fermeture rapide de la connexion. Fermer juste la commande ne ferme pas la connexion:

using (SqlConnection con = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, con))
         {
             cmd.CommandType = CommandType.StoredProcedure;
             cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
             cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
             cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
             cmd.Connection.Open();

             DataSet dset = new DataSet();
             new SqlDataAdapter(cmd).Fill(dset);
             this.gridDataSource.DataSource = dset.Tables[0];
         }

Pour répondre à votre question, vous pouvez faire la même chose dans un bloc finally, mais cela permet de bien visualiser le code et de vous assurer de ne pas oublier de nettoyer.

18
TheSoftwareJedi

Lorsque IO fonctionne, je code pour expect une exception.

SqlConnection conn = null;
SqlCommand cmd = null;

try
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;

        conn.Open(); //opens connection

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch(Exception ex)
{
    Logger.Log(ex);
    throw;
}
finally
{
    if(conn != null)
        conn.Dispose();

        if(cmd != null)
        cmd.Dispose();
}

Edit: Pour être explicite, j’évite ici le blocage using car j’estime important de se connecter dans de telles situations. L'expérience m'a appris que vous ne savez jamais quel genre d'exception bizarre pourrait apparaître. Se connecter dans cette situation peut vous aider à détecter une impasse ou à rechercher les modifications apportées à un schéma qui affectent une partie peu utilisée et peu testée de votre base de code, ou un certain nombre d'autres problèmes.

Edit 2: On peut affirmer qu'un bloc using pourrait encapsuler un try/catch dans cette situation, ce qui est tout à fait valide et fonctionnellement équivalent. Cela revient vraiment à la préférence. Voulez-vous éviter la nidification supplémentaire au détriment de la gestion de votre propre élimination? Ou engagez-vous l’emboîtement supplémentaire nécessaire à l’élimination automatique. Je pense que le premier est plus propre, donc je le fais de cette façon. Cependant, je ne réécris pas ce dernier si je le trouve dans la base de code dans laquelle je travaille.

Edit 3: Je souhaite vraiment, vraiment, que MS ait créé une version plus explicite de using () qui rende plus intuitif ce qui se passe réellement et donne plus de flexibilité dans ce cas. Considérons le code imaginaire suivant:

SqlConnection conn = null;
SqlCommand cmd = null;

using(conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString),
          cmd = new SqlCommand(reportDataSource, conn)
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString);
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
        cmd.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch(Exception ex)
{
    Logger.Log(ex);
    throw;
}

Une instruction using crée simplement un try/finally avec des appels Dispose () dans le finally. Pourquoi ne pas donner au développeur un moyen unifié d’éliminer et de gérer les exceptions?

57
Jason Jackson

Il peut ne pas y avoir d’avantage à utiliser une instruction using dans ce cas si vous allez quand même avoir un bloc try/catch/finally. Comme vous le savez, l'instruction using est un sucre syntaxique pour une try/finally qui dispose de l'objet IDisposable. Si vous voulez quand même avoir votre try/finally, vous pouvez certainement faire la Dispose vous-même.

Cela se résume essentiellement à du style: votre équipe sera peut-être plus à l'aise avec les instructions using ou les instructions using pour rendre le code plus net.

Mais, si le cache que cache la déclaration using se trouve de toute façon, allez-y et gérez les choses vous-même si tel est votre préférence.

14
Michael Burr

Si votre code ressemble à ceci:

using (SqlCommand cmd = new SqlCommand(...))
{
  try
  {
    /* call stored procedure */
  }
  catch (SqlException ex)
  {
    /* handles the exception. does not rethrow the exception */
  }
}

Ensuite, je le referrais pour utiliser essayer .. attraper .. enfin à la place.

SqlCommand cmd = new SqlCommand(...)
try
{
  /* call stored procedure */
}
catch (SqlException ex)
{
  /* handles the exception and does not ignore it */
}
finally
{
   if (cmd!=null) cmd.Dispose();
}

Dans ce scénario, je gérerais l'exception, je n'ai donc pas d'autre choix que d'ajouter cet try..catch, je pourrais aussi bien mettre la clause finally et me sauvegarder un autre niveau d'imbrication. Notez que je dois faire quelque chose dans le bloc catch et ne pas ignorer l'exception.

6
jop

Expliquant ce qu'a dit Chris Ballance, la section 15.13 de la spécification C # (ECMA-334 version 4) indique "Une instruction using est traduite en trois parties: acquisition, utilisation et élimination. L'utilisation de la ressource est implicitement incluse dans une instruction try qui une clause finally. Cette clause finally dispose de la ressource. Si une ressource nulle est acquise, aucun appel à Dispose n'est effectué et aucune exception n'est levée. "

La description est proche de 2 pages - mérite une lecture.

D'après mon expérience, SqlConnection/SqlCommand peut générer des erreurs de tant de façons qu'il est presque nécessaire de gérer les exceptions générées davantage que de gérer le comportement attendu. Je ne suis pas sûr de vouloir utiliser la clause using ici, car je voudrais pouvoir gérer moi-même le cas de ressource nulle.

5
Kevin Haines

utiliser ne signifie pas attraper des exceptions. Il s'agit de disposer correctement des ressources qui ne sont pas visibles par le ramasse-miettes.

4
Amy B

un problème avec "using" est qu'il ne gère pas les exceptions ... Si les concepteurs de "using" ajoutaient éventuellement "catch" à sa syntaxe, comme ci-dessous pseudocode, il serait beaucoup plus utile:

using (...MyDisposableObj...)
{

   ... use MyDisposableObj ...

catch (exception)

   ... handle exception ...

}

it could even have an optional "finally" clause to cleanup anything other than the "MyDisposableObj" allocated at the beginning of the "using" statement... like:

using (...MyDisposableObj...)
{

   ... use MyDisposableObj ...
   ... open a file or db connection ...

catch (exception)

   ... handle exception ...

finally

   ... close the file or db connection ...

}

vous n'aurez toujours pas besoin d'écrire du code pour vous débarrasser de MyDisposableObj b/c, il serait traité par using...

Comment faire comme ça?

4
Rick

Il y a beaucoup de bonnes réponses ici, mais je ne pense pas que cela ait encore été dit.

Quoi qu'il en soit, la méthode "Dispose" SERA appelée sur l'objet dans le bloc "using". Si vous mettez une instruction return ou générez une erreur, le "Dispose" sera appelé.

Exemple:

J'ai créé une classe appelée "MyDisposable", qui implémente IDisposable et effectue simplement une Console.Write Il toujours écrit sur la console même dans tous ces scénarios:

using (MyDisposable blah = new MyDisposable())
{
    int.Parse("!"); // <- calls "Dispose" after the error.

    return; // <-- calls Dispose before returning.
}
2
Timothy Khouri

Oui, vous aurez toujours besoin d'attraper des exceptions. L'avantage du bloc using est que vous ajoutez une portée à votre code. Vous dites: "Dans ce bloc de code, faites des choses et quand il arrive à la fin, fermez et disposez des ressources"

Ce n'est pas du tout nécessaire, mais cela définit vos intentions à toute autre personne utilisant votre code, et cela permet également de ne pas laisser les connexions, etc. ouvertes par erreur.

2
JamesSugrue

Pour votre information, dans cet exemple spécifique, étant donné que vous utilisez une connexion ADO.net et un objet Command, n'oubliez pas que l'instruction using exécute simplement Command.Dispose et Connection.Dispose () qui ne ferment pas réellement la connexion, mais libère simplement le fichier dans le pool de connexions ADO.net pour qu'il soit réutilisé par la connexion suivante.open ... ce qui est bien et que vous devez absolument le faire, bc sinon, la connexion restera inutilisable jusqu'à ce que la mémoire cache collector le libère dans le pool, ce qui peut ne pas se faire avant de nombreuses autres demandes de connexion, qui seraient sinon obligées de créer de nouvelles connexions même s'il en existe une inutilisée en attente de récupération. 

1
Charles Bretana

L’instruction using est en fait transformée en un bloc try/finally par le compilateur dans lequel le paramètre du bloc using est supprimé tant qu’il implémente l’interface IDisposable. En plus de s'assurer que les objets spécifiés sont correctement disposés quand ils tombent en dehors de la portée, il n'y a vraiment aucune capture d'erreur obtenue en utilisant cette construction.

Comme mentionné dans TheSoftwareJedi ci-dessus, vous devez vous assurer que les objets SqlConnection et SqlCommand sont tous deux correctement supprimés. L'empilement des deux en un seul bloc utilisateur est un peu salissant, et pourrait ne pas faire ce que vous pensez qu'il fait.

Veillez également à utiliser le bloc try/catch comme logique. C’est une odeur de code qui me déplaît particulièrement dans le nez et qui est souvent utilisée par les débutants ou par ceux qui se pressent de respecter le délai imparti.

1
Chris Ballance

Je choisirais quand et quand ne pas utiliser la déclaration using en fonction de la ressource avec laquelle je traite. Dans le cas d'une ressource limitée, telle qu'une connexion ODBC, je préférerais utiliser T/C/F pour pouvoir enregistrer des erreurs significatives au moment où elles se sont produites. Laisser les erreurs de pilotes de base de données revenir au client et éventuellement être perdues lors du wrapping des exceptions de niveau supérieur est sous optimal. 

T/C/F vous assure que la ressource est gérée comme vous le souhaitez. Comme certains l'ont déjà mentionné, l'instruction using ne fournit pas de gestion des exceptions, elle garantit simplement la destruction de la ressource. La gestion des exceptions est une structure de langage sous-estimée et sous-estimée qui fait souvent la différence entre le succès et l'échec d'une solution. 

1
SteveK

Donc, fondamentalement, "utiliser" est exactement le même que "Essayer/attraper/enfin", mais beaucoup plus souple pour la gestion des erreurs.

0
Craig

Une correction mineure à l'exemple: SqlDataAdapter doit également être instanciée dans une instruction using:

using (SqlConnection con = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, con))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    con.Open();

    DataSet dset = new DataSet();
    using (SqlDataAdapter adapter = new SqlDataAdapter(cmd))
    {
        adapter.Fill(dset);
    }
    this.gridDataSource.DataSource = dset.Tables[0];
}
0
John Saunders

Si l'appelant de votre fonction est responsable du traitement des exceptions, l'instruction using est un moyen agréable de s'assurer que les ressources sont nettoyées, quel que soit le résultat. 

Cela vous permet de placer le code de gestion des exceptions aux limites des couches/assemblages et d'éviter que d'autres fonctions ne deviennent trop encombrées.

Bien sûr, cela dépend vraiment des types d'exceptions générées par votre code. Parfois, vous devriez utiliser try-catch-finally plutôt qu'une instruction using. Mon habitude est de toujours commencer par une instruction using pour IDisposables (ou que les classes qui contiennent IDisposables implémentent également l'interface) et d'ajouter try-catch-finally en fonction des besoins.

0
Andrew Kennan

Tout d'abord, votre exemple de code devrait être:

using (SqlConnection conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, conn))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    cmd.Connection.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}

Avec le code de votre question, une exception créant la commande entraînera la suppression de la connexion que vous venez de créer. Avec ce qui précède, la connexion est correctement éliminée.

Si vous devez gérer des exceptions dans construction de la connexion et de la commande (ainsi que lors de leur utilisation), vous devez effectivement envelopper le tout dans un essai/catch:

try
{
    using (SqlConnection conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
    using (SqlCommand cmd = new SqlCommand(reportDataSource, conn))
    {
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
        cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
        cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
        cmd.Connection.Open();

        DataSet dset = new DataSet();
        new SqlDataAdapter(cmd).Fill(dset);
        this.gridDataSource.DataSource = dset.Tables[0];
    }
}
catch (RelevantException ex)
{
    // ...handling...
}

Mais vous n'avez pas besoin de gérer le nettoyage conn ou cmd; c'est déjà fait pour vous.

Contraste avec la même chose sans using:

SqlConnection conn = null;
SqlCommand cmd = null;
try
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString);
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    cmd.Connection.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch (RelevantException ex)
{
    // ...handling...
}
finally
{
    if (cmd != null)
    {
        try
        {
            cmd.Dispose();
        }
        catch { }
        cmd = null;
    }
    if (conn != null)
    {
        try
        {
            conn.Dispose();
        }
        catch { }
        conn = null;
    }
}
// And note that `cmd` and `conn` are still in scope here, even though they're useless

Je sais ce que je préfère écrire. :-)

0
T.J. Crowder