Je suis relativement nouveau en C #.
Je me demandais s'il y avait une différence entre ceci
// style 2
Stream stream_output = File.Create("output.txt");
Stream stream_input = File.OpenRead("input.txt");
using (Stream stream_dst = stream_output)
using (Stream stream_src = stream_input)
{
stream_src.CopyTo(stream_dst);
}
Et le suivant
// style 1
using (Stream stream_dst = File.Create("output.txt"))
using (Stream stream_src = File.OpenRead("input.txt"))
{
stream_src.CopyTo(stream_dst);
}
3 Réponses :
Techniquement, ils sont différents mais c'est mineur.
Le point d'un using statement est de garantir l'élimination des types gérés qui accèdent à des ressources non gérées.
STYLE 1:
La déclaration et l'initialisation dans une instruction (bloc) using garantit que .Dispose () est appelé sur les objets IDisposable déclarés et initialisés dans il. Même si une exception se produit. Cette approche empêche l'accès à ces objets en dehors de la portée du bloc using .
using (Stream stream_dst = File.Create("output.txt"), Stream stream_src = File.OpenRead("input.txt"))
{
stream_src.CopyTo(stream_dst);
}
STYLE 2: p>
Déclarer et initialiser en dehors d'une instruction using (bloc) et leur déclarer et initialiser des objets basés sur eux à l'intérieur d'un bloc using autorisera toujours les objets initiaux à éliminer correctement, cependant si une exception devait survenir avant le bloc using , les objets ne seront pas supprimés par le bloc using . Il vous permet également de référencer les objets potentiellement supprimés après les avoir utilisés dans le bloc using , ce qui pourrait vous causer des maux de tête à l'avenir. Si cela se produit, vous verrez probablement un message d'exception relatif à l'accès à une fermeture supprimée .
Stream stream_output = File.Create("output.txt");
Stream stream_input = File.OpenRead("input.txt");
using (Stream stream_dst = stream_output)
using (Stream stream_src = stream_input)
{
stream_src.CopyTo(stream_dst);
}
De la documentation Microsoft sur l'utilisation du style 2:
Vous pouvez instancier l'objet de ressource, puis passer la variable à l'instruction using , mais ce n'est pas une bonne pratique. Dans ce cas, une fois que le contrôle a quitté le bloc using , l'objet reste dans la portée mais n'a probablement pas accès à ses ressources non gérées. En d'autres termes, il n'est plus complètement initialisé. Si vous essayez d'utiliser l'objet en dehors du bloc using , vous risquez de provoquer la levée d'une exception. Pour cette raison, il est généralement préférable d'instancier l'objet dans l'instruction using et de limiter sa portée au bloc using.
Il est intéressant de noter:
Selon la documentation, vous pouvez déclarer plusieurs instances d'un type dans une instruction using. Ce que j'en retire, c'est que vous devriez pouvoir faire la déclaration d'instruction using comme ceci:
// *** Declaration and Instantiation within using statement ***
// *** prevents access to these variables outside of the scope ***
// *** of the using statement. ***
using (Stream stream_dst = File.Create("output.txt"))
using (Stream stream_src = File.OpenRead("input.txt"))
{
stream_src.CopyTo(stream_dst);
}
Juste pour clarifier, les variables stream_output et stream_input deviendront null dans la version 2 après la fin de l'instruction using? Ou seront-ils simplement «fermés»?
@AlanSTACK Très bonne question. Ils ne seront pas null sauf si vous écrivez un code qui les définit explicitement sur null. C'est probablement la raison la plus importante pour éviter le style 2: les références restent dans la portée après avoir été supprimées. Il n'y a rien à gagner à laisser cela se produire, et il devient possible pour un programmeur inattentif d'essayer de les utiliser après leur élimination.
@AlanSTACK J'ai mis à jour la réponse pour clarifier qu'un peu, et Ed Plunkett a raison, les objets ne sont pas définis sur null, mais ils ne sont également plus complètement initialisés.
@NullReff Je n'ai jamais connu de multiples initialisations séparées par des virgules dans une utilisation. C'est pratique. Merci.
@EdPlunkett Honnêtement, je viens de le trouver lorsque je saisis le lien de la documentation pour cette réponse. Je pense aussi que c'est pratique et honnêtement étourdi de commencer à l'utiliser.
Dans le deuxième exemple, l'objet ne sera pas supprimé correctement si une exception se produit après File.Create mais avant l'instruction using correspondante. Dans ce code, une possibilité évidente serait que File.OpenRead lève une exception car "input.txt" n'a pas été trouvé.
Garantir que la méthode Dispose est appelée même si des exceptions se produisent est le plus grand avantage d'une instruction using par rapport à un simple appel de Dispose () à la fin de la méthode. Séparer la création d'objet du bloc using a de fortes chances de briser cette garantie.
De plus, comme d'autres l'ont noté, instancier l'objet jetable à l'intérieur de l'instruction using comme dans le premier exemple limite la portée de la variable afin que l'objet ne soit pas accessible après avoir été supprimé. C'est aussi simplement plus court et plus propre, du moins dans ces exemples.
Ceci :
Stream stream_output = File.Create("output.txt");
Stream stream_input = File.OpenRead("input.txt");
using (Stream stream_dst = stream_output)
using (Stream stream_src = stream_input)
{
stream_src.CopyTo(stream_dst);
}
est le même que ceci:
using (Stream stream_dst = File.Create("output.txt"))
{
using (Stream stream_src = File.OpenRead("input.txt"))
{
stream_src.CopyTo(stream_dst);
}
}
Les deux garantissent que si un objet est créé, il sera également éliminé .
Dans ce cas:
using (Stream stream_dst = File.Create("output.txt"))
using (Stream stream_src = File.OpenRead("input.txt"))
{
stream_src.CopyTo(stream_dst);
}
stream_output sera supprimé car vous supprimez stream_dst , et c'est le même flux. Mais si une exception est levée avant ce bloc using alors stream_output ne sera pas supprimé.
Un bloc using nous permet de combiner la création / l'acquisition d'un objet avec la garantie de sa suppression. Si le bloc using suit la création / l'acquisition de l'objet, il le supprimera toujours, mais seulement si aucune exception n'est levée avant que l'instruction using ne soit exécutée. Nous ne séparerions généralement pas les deux car les combiner est l'avantage de utiliser .
La première approche est considérée comme meilleure car vous ne pourrez pas utiliser ces variables, même si vous avez tenté par erreur. Mais vous pouvez utiliser la deuxième approche
Pourquoi la variable secondaire? Le second peut être réduit à
en utilisant (stream_output) {...}. L'instanciation ne doit pas se produire dans l'instruction using, mais elle devrait.Quel serait l'intérêt d'instancier les variables séparément? Si vous utilisez l'option 2, lorsque l'exécution quitte le bloc
using, les objets seront supprimés et ne seront plus utilisables ailleurs.L'option 2 est acceptable mais si la variable n'est jamais créée que pour être ensuite passée dans une instruction
usingalors c'est un gaspillage de mémoire ¯ \ _ (ツ) _ / ¯@James Pouvez-vous préciser où la mémoire supplémentaire sera utilisée par l'option 2, et dans quelle mesure?
@EdPlunkett Option 2 crée une variable supplémentaire pour stocker une copie de la référence
Stream- cela fait un moment que je n'ai pas vécu dans le monde .NET mais IIRC ce serait environ 4 octets pour une référence.@James Ce n'est pas vraiment du "gaspillage de mémoire", d'autant plus que ça revient hors de portée. C'est perdu dans le bruit.
@EdPlunkett vous pouvez être sarcastique à propos de tout ce que vous voulez, et je comprends parfaitement que c'est une micro-optimisation ... mais le fait est toujours d'actualité, pourquoi gaspiller 4 octets quand vous n'êtes pas obligé? Pourquoi écrire plus de code alors que ce n'est pas nécessaire?
@EdPlunkett c'est votre interprétation, je l'ai utilisé comme un autre exemple de la raison pour laquelle l'option 2 n'est pas recommandée - vous semblez faire un débat à partir de rien ici étant donné que nous chantons tous les deux la même feuille d'hymne - l'option 1 est recommandée pour raisons diverses. De plus, vous semblez laisser entendre que c'est une mauvaise chose de rendre les développeurs (nouveaux ou anciens) conscients de la façon dont ils allouent la mémoire, à ce stade, je ne suis pas d'accord avec vous :)
@James Le JIT serait entièrement dans son droit de réutiliser toute mémoire allouée pour les locaux stream_output et stream_input, il n'est donc pas clair pour moi qu'il y ait même une différence fonctionnelle dans la quantité de mémoire utilisée dans cet exemple.
En effet - la différence sémantique de savoir si le premier flux est supprimé si le second flux ne peut pas être créé est beaucoup, beaucoup plus importante que l'espace pris pour une variable locale.
@JonSkeet entièrement d'accord, je ne débat pas du tout de cela, et je n'essaie pas non plus de débattre de la raison qui est la plus importante que l'autre - si quelque chose j'essayais simplement de donner un autre exemple de pourquoi l'option 1 est préférable. Mais il est impliqué dans un débat sur un commentaire complètement mineur / passant, un rappel brutal de la raison pour laquelle j'ai arrêté de contribuer sur les threads .NET.
@MikeZboray alors comment réutilise-t-il la mémoire pour que les variables ne soient pas encore désallouées?
@James Le JIT est libre de remarquer qu'il peut simplement réutiliser le même stockage pour les nouvelles variables. Il n'est pas nécessaire que, parce que vous avez déclaré un temporaire nommé, un espace de pile lui est alloué.
@MikeZboray cela ne me semble pas juste, si une référence var est comparable à une clé d'une maison, comment puis-je vous donner une copie de la clé et quelqu'un d'autre si elles sont mutuellement exclusives? Comment cela fonctionne-t-il si je passe l'une de ces références dans une fonction? Vous suggérez si j'ai défini 10 vars ref (tous pointant vers la même référence) qui alloueraient seulement 4 octets et non 10 ^ 4? Avez-vous des garanties qui expliquent comment cela fonctionne? Je suis intrigué.
@James Pour étirer un peu l'analogie, vous ne faites pas du tout de copie de la clé. Vous dites à Alice de faire une copie d'une clé pour qu'elle puisse la donner à Bob. Supposons que Bob ne se présente jamais, peut-être qu'Alice le sait et décide de ne pas faire de copie parce que c'est du travail supplémentaire. C'est ce que fait le JIT. Je n'ai pas de spécification pour vous, mais ce type d'optimisation est fréquemment mentionné dans divers articles et discussions .
@MikeZboray Je suis pleinement conscient du travail des JIT, et rien dans les liens que vous avez fournis ne garantit que la variable serait optimisée (en particulier étant donné qu'ils sont nommés différemment). Quoi qu'il en soit, je pense que le point que je voulais dire a été perdu dans l'éther ici, simplement parce que le JIT peut nettoyer après vous, il est toujours bon de comprendre les implications du code que vous écrivez, en particulier lorsqu'il n'est pas nécessaire . Lorsque vous travaillez suffisamment longtemps sur des systèmes embarqués, vous apprenez à apprécier l'importance de 4 octets ...
@ James Oh, je ne voulais pas suggérer qu'il doit faire cette optimisation, juste que ce serait valide et probable. Cela peut être différent en C ou C ++. FWIW Je suis d'accord avec vous que la ligne de pensée que "ses seulement quatre octets alors qu'importe?" N'est pas particulièrement utile car dans certains contextes, ces détails importent.