Imbriqué à l'aide d'instructions en C #


316

Je travaille sur un projet. Je dois comparer le contenu de deux fichiers et voir s'ils correspondent exactement.

Avant beaucoup de vérification d'erreurs et de validation, mon premier brouillon est:

  DirectoryInfo di = new DirectoryInfo(Environment.CurrentDirectory + "\\TestArea\\");
  FileInfo[] files = di.GetFiles(filename + ".*");

  FileInfo outputFile = files.Where(f => f.Extension == ".out").Single<FileInfo>();
  FileInfo expectedFile = files.Where(f => f.Extension == ".exp").Single <FileInfo>();

  using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
  {
    using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
    {
      while (!(outFile.EndOfStream || expFile.EndOfStream))
      {
        if (outFile.ReadLine() != expFile.ReadLine())
        {
          return false;
        }
      }
      return (outFile.EndOfStream && expFile.EndOfStream);
    }
  }

Il semble un peu étrange d'avoir des usinginstructions imbriquées .

Y a-t-il une meilleure manière de faire cela?


Je pense que j'ai peut-être trouvé une manière syntaxiquement plus propre de déclarer ceci en utilisant l'instruction, et cela semble fonctionner pour moi? l'utilisation de var comme type dans l'instruction using au lieu de IDisposable semble me permettre d'instancier mes deux objets et d'appeler leurs propriétés et méthodes de la classe avec laquelle ils sont alloués, comme en utilisant (var uow = UnitOfWorkType1 (), uow2 = UnitOfWorkType2 ()) {}
Caleb

Réponses:


557

La façon préférée de le faire est de ne mettre une accolade ouvrante {qu'après la dernière usinginstruction, comme ceci:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead())) 
{
    ///...
}

10
Nettoyeur? et ne vous oblige pas non plus à utiliser les mêmes types .. Je le fais toujours de cette façon même si les types correspondent pour la lisibilité et la cohérence.
meandmycode

7
@Hardryv: le formatage automatique de Visual Studio le supprime. L'idée est de ressembler à une liste de déclarations de variables.
SLaks

41
Je ne sais pas si je trouve cela plus lisible du tout. Si quoi que ce soit, il brise l'apparence du code imbriqué. Et il semble que la première instruction using soit vide et inutilisée. Mais, je suppose que ce qui fonctionne ...: /
Jonathon Watney

10
@Bryan Watts, les "contraires" expriment peut-être de réelles préférences. Il est très probable qu'un groupe différent de développeurs aurait été dissident si l'imbrication avait été recommandée. La seule façon de savoir est de relancer l'expérience dans un univers parallèle.
Dan Rosenstark

6
@fmuecke: Ce n'est pas très vrai; ça va marcher. Les règles IDisposablestipulent que Dispose()deux appels ne devraient rien faire. Cette règle ne s'applique qu'en cas de produits jetables mal écrits.
SLaks

138

Si les objets sont du même type, vous pouvez effectuer les opérations suivantes

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()), 
                    expFile = new StreamReader(expectedFile.OpenRead()))
{
    // ...
}

1
Eh bien, ils sont tous du même type s'ils sont tous IDisposables, peut-être qu'un casting fonctionnerait?
jpierson

8
@jpierson qui fonctionne, oui, mais quand vous appelez les IDisposableobjets de l'intérieur du bloc using, nous ne pouvons appeler aucun des membres de la classe (sans cast, ce qui bat le point imo).
Connell

IDisposable est un type, il suffit donc de l'utiliser comme type pour avoir une liste de types mixtes, comme on le voit dans quelques autres réponses.
Chris Rollins

33

Lorsque les IDisposables sont du même type, vous pouvez effectuer les opérations suivantes:

 using (StreamReader outFile = new StreamReader(outputFile.OpenRead()), 
     expFile = new StreamReader(expectedFile.OpenRead()) {
     // ...
 }

La page MSDN sur usingcontient de la documentation sur cette fonction de langue.

Vous pouvez effectuer les opérations suivantes, que les IDisposables soient du même type ou non :

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamWriter anotherFile = new StreamReader(anotherFile.OpenRead()))
{ 
     // ...
}

18

si cela ne vous dérange pas de déclarer les variables de votre bloc using avant le bloc using, vous pouvez les déclarer toutes dans la même instruction using.

    Test t; 
    Blah u;
    using (IDisposable x = (t = new Test()), y = (u = new Blah())) {
        // whatever...
    }

De cette façon, x et y ne sont que des variables d'espace réservé de type IDisposable pour le bloc using à utiliser et vous utilisez t et u dans votre code. Je pensais juste que je mentionnerais.


3
J'ai l'impression que ce serait déroutant pour un nouveau développeur qui regarde votre code.
Zack

5
Cela peut être une mauvaise pratique; cela a pour effet secondaire que les variables existent toujours même après la libération des ressources non gérées. Selon la référence C # de Microsoft, «Vous pouvez instancier l'objet ressource, puis transmettre la variable à l'instruction using, mais ce n'est pas une bonne pratique. Dans ce cas, l'objet reste dans la portée une fois que le contrôle quitte le bloc using même s'il le fera n’ont probablement plus accès à ses ressources non gérées. "
Robert Altman du

@RobertAltman Vous avez raison, et en vrai code j'utiliserais une autre approche (probablement celle de Gavin H). Ceci est juste une alternative moins préférable.
Botz3000

Vous pouvez simplement déplacer les déclarations à l'intérieur de l'utilisation avec des typecasts. Serait-ce mieux?
Timothy Blaisdell

9

Si vous souhaitez comparer les fichiers efficacement, n'utilisez pas du tout StreamReaders, et les utilisations ne sont pas nécessaires - vous pouvez utiliser des lectures de flux de bas niveau pour extraire des tampons de données à comparer.

Vous pouvez également comparer des éléments tels que la taille du fichier pour détecter rapidement différents fichiers afin de vous éviter d'avoir à lire toutes les données également.


Oui, vérifier la taille du fichier est une bonne idée, vous fait gagner du temps ou lire tous les octets. (+1)
TimothyP

9

L'instruction using fonctionne hors de l'interface IDisposable, donc une autre option pourrait être de créer un type de classe composite qui implémente IDisposable et a des références à tous les objets IDisposable que vous mettriez normalement dans votre instruction using. L'inconvénient est que vous devez d'abord déclarer vos variables et en dehors de la portée pour qu'elles soient utiles dans le bloc using nécessitant plus de lignes de code que certaines autres suggestions n'en auraient besoin.

Connection c = new ...; 
Transaction t = new ...;

using (new DisposableCollection(c, t))
{
   ...
}

Le constructeur de DisposableCollection est un tableau de paramètres dans ce cas, vous pouvez donc en ajouter autant que vous le souhaitez.


7

Vous pouvez également dire:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
   ...
}

Mais certaines personnes pourraient trouver cela difficile à lire. BTW, comme une optimisation de votre problème, pourquoi ne vérifiez-vous pas que les tailles de fichier sont la même taille avant de passer ligne par ligne?


6

Vous pouvez omettre les crochets sur tous sauf le plus intérieur en utilisant:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
  while (!(outFile.EndOfStream || expFile.EndOfStream))
  {
    if (outFile.ReadLine() != expFile.ReadLine())
    {
      return false;
    }
  }
}

Je pense que c'est plus propre que de mettre plusieurs du même type dans la même utilisation, comme d'autres l'ont suggéré, mais je suis sûr que beaucoup de gens penseront que cela prête à confusion


6

Vous pouvez regrouper plusieurs objets jetables en une seule instruction using avec des virgules:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()), 
       expFile = new StreamReader(expectedFile.OpenRead()))
{

}

5

Il n'y a rien d'étrange à cela. usingest une manière abrégée d'assurer l'élimination de l'objet une fois le bloc de code terminé. Si vous avez un objet jetable dans votre bloc extérieur que le bloc intérieur doit utiliser, cela est parfaitement acceptable.

Modifier: trop lent sur la saisie pour afficher un exemple de code consolidé. +1 à tout le monde.


5

Et pour ajouter à la clarté, dans ce cas, puisque chaque instruction successive est une instruction unique, (et non un bloc), vous pouvez omettre tous les crochets:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
  using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
    while (!(outFile.EndOfStream || expFile.EndOfStream))  
       if (outFile.ReadLine() != expFile.ReadLine())    
          return false;  

Solution intéressante; faire cela / même en utilisant le 1 jeu de supports au niveau le plus bas peut-être, accomplit le même objectif que de les empiler justifiés à gauche (OMI plus propre), tout en répondant au désir d'emboîtement cosmétique que d'autres ont mentionné pour montrer toute subordination.
user1172173

5

Depuis C # 8.0, vous pouvez utiliser une déclaration using .

using var outFile = new StreamReader(outputFile.OpenRead());
using var expFile = new StreamReader(expectedFile.OpenRead());
while (!(outFile.EndOfStream || expFile.EndOfStream))
{
    if (outFile.ReadLine() != expFile.ReadLine())
    {
         return false;
    }
}
return (outFile.EndOfStream && expFile.EndOfStream);

Ceci éliminera les variables utilisatrices à la fin de la portée des variables, c'est-à-dire à la fin de la méthode.


3

Celles-ci apparaissent de temps en temps lorsque je code également. Vous pourriez envisager de déplacer la deuxième instruction using dans une autre fonction?


3

Demandez-vous également s'il existe une meilleure façon de comparer les fichiers? Je préfère calculer un CRC ou un MD5 pour les deux fichiers et les comparer.

Par exemple, vous pouvez utiliser la méthode d'extension suivante:

public static class ByteArrayExtender
    {
        static ushort[] CRC16_TABLE =  { 
                      0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241, 
                      0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440, 
                      0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40, 
                      0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841, 
                      0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40, 
                      0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41, 
                      0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641, 
                      0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040, 
                      0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240, 
                      0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441, 
                      0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41, 
                      0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840, 
                      0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41, 
                      0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40, 
                      0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640, 
                      0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041, 
                      0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240, 
                      0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441, 
                      0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41, 
                      0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840, 
                      0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41, 
                      0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40, 
                      0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640, 
                      0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041, 
                      0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241, 
                      0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440, 
                      0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40, 
                      0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841, 
                      0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40, 
                      0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41, 
                      0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641, 
                      0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 };


        public static ushort CalculateCRC16(this byte[] source)
        {
            ushort crc = 0;

            for (int i = 0; i < source.Length; i++)
            {
                crc = (ushort)((crc >> 8) ^ CRC16_TABLE[(crc ^ (ushort)source[i]) & 0xFF]);
            }

            return crc;
        }

Une fois que vous avez fait cela, il est assez facile de comparer les fichiers:

public bool filesAreEqual(string outFile, string expFile)
{
    var outFileBytes = File.ReadAllBytes(outFile);
    var expFileBytes = File.ReadAllBytes(expFile);

    return (outFileBytes.CalculateCRC16() == expFileBytes.CalculateCRC16());
}

Vous pouvez utiliser la classe System.Security.Cryptography.MD5 intégrée, mais le hachage calculé est un octet [], vous devrez donc toujours comparer ces deux tableaux.


2
Au lieu de prendre un tableau d'octets, la méthode doit prendre un Streamobjet et appeler la ReadByteméthode jusqu'à ce qu'elle renvoie -1. Cela permettra d'économiser de grandes quantités de mémoire pour les fichiers volumineux.
SLaks

Comment calculeriez-vous alors le crc sur tous les octets?
TimothyP

Oh, peu importe ce que j'ai dit: p Thnx, je vais changer cela dans mon code: p Nous ne l'utilisons que pour des données <1000 octets, nous n'avons donc pas encore remarqué de problèmes, mais cela changera quand même
TimothyP

Chaque fois que vous appelez, ReadBytela position du flux avance d'un octet. Par conséquent, si vous continuez à l'appeler jusqu'à ce qu'il renvoie -1 (EOF), il vous donnera chaque octet du fichier. msdn.microsoft.com/en-us/library/system.io.stream.readbyte.aspx
SLaks

7
L'utilisation d'un CRC est idéale si vous souhaitez comparer plusieurs fichiers plusieurs fois, mais pour une seule comparaison, vous devez lire les deux fichiers dans leur intégralité pour calculer les CRC - Si vous comparez les données en petits morceaux, vous pouvez quitter la comparaison comme dès que vous trouvez un octet différent.
Jason Williams

3

De plus, si vous connaissez déjà les chemins, il est inutile d'analyser le répertoire.

Au lieu de cela, je recommanderais quelque chose comme ceci:

string directory = Path.Combine(Environment.CurrentDirectory, @"TestArea\");

using (StreamReader outFile = File.OpenText(directory + filename + ".out"))
using (StreamReader expFile = File.OpenText(directory + filename + ".exp"))) 
{
    //...

Path.Combine ajoutera un dossier ou un nom de fichier à un chemin d'accès et s'assurera qu'il y a exactement une barre oblique inverse entre le chemin d'accès et le nom.

File.OpenTextouvrira un fichier et en créera un StreamReaderen une seule fois.

En préfixant une chaîne avec @, vous pouvez éviter d'avoir à échapper à chaque barre oblique inverse (par exemple, @"a\b\c")


3

Je pense que j'ai peut-être trouvé une manière syntaxiquement plus propre de déclarer ceci en utilisant l'instruction, et cela semble fonctionner pour moi? utiliser var comme type dans l'instruction using au lieu d'IDisposable semble inférer dynamiquement du type sur les deux objets et me permet d'instancier mes deux objets et d'appeler leurs propriétés et méthodes de la classe avec laquelle ils sont alloués, comme dans

using(var uow = new UnitOfWorkType1(), uow2 = new UnitOfWorkType2()){}.

Si quelqu'un sait pourquoi n'est pas bon, s'il vous plaît faites le moi savoir


1
Plusieurs sur une même ligne fonctionnent si toutes les choses sont du même type. Les types mixtes doivent être divisés en plusieurs à l'aide de () s. Mais cela ne fonctionne pas avec var, vous devez spécifier un type (spécification C # 5, p237)
Chris F Carroll

0

C'est le mode d'utilisation normal et fonctionne parfaitement. Bien qu'il existe d'autres façons de mettre en œuvre cela. Presque toutes les réponses sont déjà présentes dans la réponse à cette question. Mais ici, je les énumère tous ensemble.

Déjà utilisé

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
  {
    using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
    {
      while (!(outFile.EndOfStream || expFile.EndOfStream))
      {
        if (outFile.ReadLine() != expFile.ReadLine())
        return false;
      }
    }
  }

Option 1

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
    using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
    {
      while (!(outFile.EndOfStream || expFile.EndOfStream))
      {
        if (outFile.ReadLine() != expFile.ReadLine())
        return false;
      }
    }
  }

Option 2

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()),
                    expFile = new StreamReader(expectedFile.OpenRead()))
   {
      while (!(outFile.EndOfStream || expFile.EndOfStream))
       {
         if (outFile.ReadLine() != expFile.ReadLine())
         return false;
       }
    }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.