113 votes

Il est abusif d’utiliser IDisposable et « utilisation » comme un moyen pour obtenir le « comportement étendue » pour la sécurité d’exception ?

Quelque chose que j'ai souvent utilisé le dos en C++ a été de laisser une classe A gérer un état d'entrée et de sortie de la condition pour une autre catégorie d' B, via l' A constructeur et le destructeur, pour s'assurer que si quelque chose dans le champ d'application a généré une exception, alors B serait un état connu quand il avait quitté. Ce n'est pas pur RAII aussi loin que l'acronyme va, mais c'est un modèle établi néanmoins.

En C#, j'ai souvent envie de le faire

class FrobbleManager
{
    ...

    private void FiddleTheFrobble()
    {
        this.Frobble.Unlock();
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
        this.Frobble.Lock();
    }
}

Ce qui doit être fait comme ceci

private void FiddleTheFrobble()
{
    this.Frobble.Unlock();

    try
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
    finally
    {
        this.Frobble.Lock();
    }
}

si je veux garantir l' Frobble de l'état lors de l' FiddleTheFrobble de rendement. Le code serait plus sympa

private void FiddleTheFrobble()
{
    using (var janitor = new FrobbleJanitor(this.Frobble))
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
}

FrobbleJanitor semble à peu près comme

class FrobbleJanitor : IDisposable
{
    private Frobble frobble;

    public FrobbleJanitor(Frobble frobble)
    {
        this.frobble = frobble;
        this.frobble.Unlock();
    }

    public void Dispose()
    {
        this.frobble.Lock();
    }
}

Et voilà comment je veux le faire. Aujourd'hui, la réalité le rattrape, puisque ce que je veux utiliser exige que l' FrobbleJanitor est utilisé avec using. Je pourrais considérer cette une de la revue de code de problème, mais quelque chose me harceler.

Question: Serait la ci-dessus sera considérée comme une utilisation abusive de l' using et IDisposable?

72voto

Eric Lippert Points 300275

Je considère cela comme un abus de l'utilisation de déclaration. Je suis conscient que je suis en minorité sur cette position.

Je considère être un abus pour trois raisons.

D'abord, parce que j'attends de cette "aide" est utilisé pour l'utilisation d'une ressource et d' en disposer lorsque vous avez terminé avec elle. Changement de programme de l'etat n'est pas l'utilisation d'une ressource et de le changer en arrière n'est pas l'élimination quoi que ce soit. Par conséquent, "l'aide" à muter et à la restauration de l'état est un abus; le code est trompeur pour le lecteur occasionnel.

Deuxièmement, parce que je m'attends à "l'aide" à être utilisé par politesse, non par nécessité. La raison pour laquelle vous utilisez "l'aide" de disposer d'un fichier lorsque vous en avez terminé avec ce n'est pas parce qu'il est nécessaire de le faire, mais parce qu'il est poli -- quelqu'un d'autre pourrait être en attente pour utiliser ce fichier, donc dire que "fait" le moralement correct chose à faire. Je pense que je devrais être en mesure de refactoriser une "aide" de sorte que l'utilisation des ressources est maintenu plus longtemps, et vendus plus tard, et que le seul effet de faire légèrement gêner les autres processus. Une "aide" bloc qui a sémantique des répercussions sur le programme de l'état est abusif car il cache un rôle important, la nécessaire mutation de programme, dans une construction qui ressemble comme il est là pour des raisons de commodité et de politesse, non par nécessité.

Et la troisième, de votre programme les actions sont déterminées par son état; le besoin de soin dans la manipulation de l'etat est précisément la raison pour laquelle nous allons avoir cette conversation en premier lieu. Considérons la manière dont nous pourrions analyser votre programme d'origine.

Étiez-vous de porter cette question à un examen de code dans mon bureau, la première question que je voudrais poser est: "est-il vraiment correct pour verrouiller le frobble si une exception est levée?" C'est flagrant à partir de votre programme, cette chose de façon agressive re-verrouille le frobble, quoi qu'il arrive. Est ce que le droit? Une exception a été levée. Le programme est dans un état inconnu. Nous ne savons pas si Foo, du Violon ou de la Barre jeté, pourquoi ils ont jeté, ou ce que les mutations qu'ils ont effectué à d'autres qui n'ont pas été nettoyés. Pouvez-vous vous convaincre moi que dans cette terrible situation, c'est toujours la bonne chose à faire pour re-verrouiller?

C'est peut-être, peut-être qu'il ne l'est pas. Mon point est qu'avec le code tel qu'il a été écrit à l'origine, le code examinateur sait poser la question. Avec le code qui utilise "l'aide", je ne sais pas à poser la question; je suppose que les "aide" bloc alloue une ressource, qu'il utilise pour un peu, et poliment dispose quand c'est fait, non pas que l' accolade de fermeture de l ' "utilisation" bloc de mutation de mon programme, dans un cadre exceptionnel cirumstance quand arbitrairement un grand nombre de programmes de l'état cohérence conditions ont été violés.

L'utilisation de "l'aide" bloc d'avoir une sémantique de l'effet de ce fragment d'un programme:

}

extrêmement significatif. Quand je regarde cette seule accolade fermante je n'ai pas tout de suite penser "que le corset a des effets secondaires qui ont des répercussions profondes sur l'état global de mon programme". Mais quand vous l'abus de "l'aide" comme ça, tout à coup, il n'.

La deuxième chose que je voudrais demander si j'ai vu votre code d'origine est "ce qui se passe si une exception est levée après le Déverrouillage, mais avant de l'essayer est entré?" Si vous utilisez un non-optimisée de l'assemblée, le compilateur peut avoir inséré un no-op instruction avant de l'essayer, et il est possible pour un abandon de thread exception à arriver sur la non-op. C'est rare, mais ça arrive dans la vraie vie, en particulier sur des serveurs web. Dans ce cas, le code de déverrouillage qui se passe, mais le verrou n'arrive jamais, parce que l'exception a été levée avant le coup d'essayer. Il est tout à fait possible que ce code est vulnérable à ce problème, et devraient en fait être écrite

bool needsLock = false;
try
{
    // must be carefully written so that needsLock is set
    // if and only if the unlock happened:

    this.Frobble.AtomicUnlock(ref needsLock);
    blah blah blah
}
finally
{
    if (needsLock) this.Frobble.Lock();
}

Encore une fois, peut-être qu'il n'a, peut-être qu'il ne le fait pas, mais je sais que de poser la question. Avec l'aide de la version, il est sensible à le même problème: un abandon de thread exception peut être levée après l'Frobble est verrouillé, mais avant de l'essayer protégé région associée avec l'aide de est entré. Mais avec l'aide de la version, je suppose que c'est un "et alors?" de la situation. C'est dommage si cela arrive, mais je suppose que les "aide" n'est là que pour être poli, de ne pas muter d'une importance vitale programme de l'état. Je suppose que si certains terrible abandon de thread exception se produit exactement au mauvais moment, puis, oh bien, le garbage collector nettoyer que la ressource est finalement par l'exécution de l'outil de finalisation.

35voto

Andras Zoltan Points 24996

Je ne le pense pas, nécessairement. IDisposable, techniquement, est destiné à être utilisé pour des choses qui ont des ressources gérées, mais alors l'aide de la directive est juste une façon ingénieuse de la mise en œuvre d'un modèle commun d' try .. finally { dispose }.

Un puriste diront "oui - il de plus en plus violents", et dans le sens puriste du terme il est; mais la plupart d'entre nous n'a pas de code à partir d'un point de vue puriste, mais à partir d'un semi-artistique. À l'aide de la 'l'aide de" construire de cette façon est tout à fait artistique, en effet, à mon avis.

Vous devriez vous cantonner à un autre de l'interface sur le dessus de IDisposable de le pousser un peu plus loin, en expliquant à d'autres développeurs pourquoi cette interface implique IDisposable.

Il y a beaucoup d'autres alternatives pour le faire, mais, en fin de compte, je ne peux pas penser à tout ce qui sera aussi soigné que cela, alors allez-y!

27voto

Samuel Jack Points 14556

Eric Gunnerson, qui était sur le langage C# de l'équipe de conception, a donné cette réponse à peu près la même question:

Doug demande:

re: Une instruction lock avec délai d'attente...

J'ai fait ce truc avant de traiter avec des modèles communs dans de nombreuses méthodes. Généralement acquisition de verrou, mais il ya quelques autres. Problème est qu'il se sent toujours comme un hack, puisque l'objet n'est pas vraiment jetables plus comme des "call-back-à-la-fin-de-un-champ-mesure".

Doug,

Lorsque nous avons décidé[sic] l'instruction d'utilisation, nous avons décidé de le nommer "l'aide" plutôt que quelque chose de plus spécifique pour l'élimination des objets de sorte qu'il pourrait être utilisé pour ce scénario.

11voto

Hans Passant Points 475940

C'est une pente glissante. IDisposable a un contrat, qui est soutenue par un outil de finalisation. Un finaliseur est inutile dans votre cas. Vous ne pouvez pas forcer le client à utiliser l'instruction d'utilisation, seuls les inciter à le faire. Vous pouvez le forcer avec une méthode comme ceci:

void UseMeUnlocked(Action callback) {
  Unlock();
  try {
    callback();
  }
  finally {
    Lock();
  }
}

Mais qui a tendance à être un peu maladroit sans lamdas. Cela dit, j'ai utilisé IDisposable comme vous l'avez fait.

Il y a cependant un détail dans votre post qui fait de ce dangereusement proche d'un anti-modèle. Vous avez mentionné que ces méthodes peuvent lever une exception. Ce n'est pas quelque chose que l'appelant ne peut ignorer. Il peut faire trois choses à ce propos:

  • Ne rien faire, l'exception n'est pas récupérable. Le cas normal. L'appel de Déverrouiller n'a pas d'importance.
  • Attraper et manipuler l'exception
  • Restauration de l'état dans son code, et laissez l'exception de laisser passer la chaîne d'appel.

Les deux derniers nécessite l'appelant explicitement à écrire un bloc try. Maintenant, l'instruction à l'aide est dans la manière. Il se pourrait bien induire un client dans un coma qui lui fait croire que votre classe est de prendre soin de l'état et pas de travail supplémentaire doit être fait. C'est presque jamais précis.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X