Je travaille sur un projet. Je dois comparer le contenu de deux fichiers et voir s’ils correspondent exactement.
Avant beaucoup de vérification des erreurs et de validation, ma première ébauche est la suivante:
DirectoryInfo di = new DirectoryInfo(Environment.CurrentDirectory + "\\TestArea\\"); FileInfo[] files = di.GetFiles(filename + ".*"); FileInfo outputFile = files.Where(f => f.Extension == ".out").Single(); FileInfo expectedFile = files.Where(f => f.Extension == ".exp").Single (); 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 nested using
instructions.
Y a-t-il une meilleure manière de faire cela?
La méthode préférée consiste à ne placer qu’une accolade ouvrante {
après la dernière instruction using
, comme ceci:
using (StreamReader outFile = new StreamReader(outputFile.OpenRead())) using (StreamReader expFile = new StreamReader(expectedFile.OpenRead())) { ///... }
Si les objects sont du même type, vous pouvez effectuer les opérations suivantes
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()), expFile = new StreamReader(expectedFile.OpenRead())) { // ... }
Lorsque les IDisposable
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 l’ using
contient de la documentation sur cette fonctionnalité de langage.
Vous pouvez effectuer les opérations suivantes, que les IDisposable
soient ou non du même type:
using (StreamReader outFile = new StreamReader(outputFile.OpenRead())) using (StreamWriter anotherFile = new StreamReader(anotherFile.OpenRead())) { // ... }
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 manière, 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.
Si vous voulez comparer les fichiers efficacement, n’utilisez pas du tout StreamReaders, et les utilisations ne sont pas nécessaires. Vous pouvez utiliser des lectures de stream de bas niveau pour extraire des tampons de données à comparer.
Vous pouvez également comparer des choses comme la taille du fichier en premier afin de détecter rapidement différents fichiers pour vous éviter d’avoir à lire toutes les données.
L’instruction using fonctionne à partir de l’interface IDisposable; une autre option pourrait être de créer un type de classe composite qui implémente IDisposable et qui contient des références à tous les objects IDisposables que vous placeriez normalement dans votre instruction using. L’inconvénient de ceci est que vous devez déclarer vos variables en premier et en dehors de la scope pour qu’elles soient utiles dans le bloc d’utilisation, ce qui nécessite plus de lignes de code que certaines des autres suggestions ne le requièrent.
Connection c = new ...; Transaction t = new ...; using (new DisposableCollection(c, t)) { ... }
Le constructeur de DisposableCollection est un tableau de parameters dans ce cas, vous pouvez donc en alimenter autant que vous le souhaitez.
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, en tant qu’optimisation de votre problème, pourquoi ne pas vérifier que la taille des fichiers est la même en premier, avant de passer ligne par ligne?
Vous pouvez omettre les parenthèses 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 cela 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 c’est déroutant
Il n’y a rien d’étrange à ce sujet. using
est un moyen rapide d’assurer l’élimination de l’object une fois le bloc de code terminé. Si vous avez un object jetable dans votre bloc externe que le bloc interne doit utiliser, cela est parfaitement acceptable.
Edit: trop lent sur la saisie pour afficher l’exemple de code consolidé. +1 à tous les autres.
Et pour append à 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;
Vous pouvez regrouper plusieurs objects à usage unique dans une instruction using avec des virgules:
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()), expFile = new StreamReader(expectedFile.OpenRead())) { }
Celles-ci arrivent de temps en temps quand je code aussi. Vous pourriez envisager de déplacer la deuxième déclaration en utilisant une autre fonction?
Demandez-vous également s’il existe un meilleur moyen 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(ssortingng outFile, ssortingng 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 comparer ces deux tableaux.
De plus, si vous connaissez déjà les chemins, il est inutile de scanner le répertoire.
Au lieu de cela, je recommanderais quelque chose comme ceci:
ssortingng directory = Path.Combine(Environment.CurrentDirectory, @"TestArea\"); using (StreamReader outFile = File.OpenText(directory + filename + ".out")) using (StreamReader expFile = File.OpenText(directory + filename + ".exp"))) { //...
Path.Combine
ajoute un dossier ou un nom de fichier à un chemin et s’assure qu’il y a exactement une barre oblique inverse entre le chemin et le nom.
File.OpenText
ouvrira un fichier et créera un StreamReader
d’un coup.
En préfixant une chaîne avec @, vous pouvez éviter de devoir échapper à chaque barre oblique inverse (par exemple, @"a\b\c"
)
Je pense que j’ai peut-être trouvé une manière syntaxiquement plus propre de déclarer cette déclaration d’utilisation, et cela semble fonctionner pour moi? L’utilisation de var comme type dans l’instruction using au lieu de IDisposable semble impliquer dynamicment le type sur les deux objects et me permet d’instancier mes deux objects 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 ce n’est pas correct, s’il vous plaît faites le moi savoir