Je travaille sur un projet. Je dois comparer le contenu de deux fichiers et voir s'ils correspondent exactement.
Avant de nombreuses erreurs de vérification et de validation, mon premier brouillon est le suivant:
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 imbriqué des instructions using
.
Y a-t-il une meilleure manière de faire cela?
La façon préférée de le faire est de ne mettre qu'une accolade d'ouverture {
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 objets sont de type 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
s 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 using
contient de la documentation sur cette fonctionnalité de langage.
Vous pouvez faire ce qui suit si les IDisposable
s sont 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 pour votre bloc using avant le bloc using, vous pouvez toutes les déclarer 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 de substitution du type IDisposable pour le bloc using à utiliser et vous utilisez t et u dans votre code. Je pensais juste que je mentionnerais.
Si vous souhaitez comparer efficacement les fichiers, n'utilisez pas du tout StreamReaders. Les utilisations ne sont donc plus nécessaires. Vous pouvez utiliser des lectures de flux de bas niveau pour extraire des tampons de données à comparer.
Vous pouvez également comparer d’abord des éléments tels que la taille du fichier pour détecter rapidement différents fichiers et vous éviter d’avoir à lire toutes les données.
L'instruction using fonctionne hors de l'interface IDisposable; une autre option pourrait donc être de créer un type de classe composite qui implémente IDisposable et comporte des références à tous les objets IDisposable que vous auriez normalement placés dans votre instruction using. L'inconvénient est que vous devez déclarer vos variables d'abord et en dehors de leur champ d'application afin qu'elles soient utiles dans le bloc using nécessitant plus de lignes de code que certaines des autres suggestions.
Connection c = new ...;
Transaction t = new ...;
using (new DisposableCollection(c, t))
{
...
}
Le constructeur de DisposableCollection est un tableau params 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, pour optimiser votre problème, pourquoi ne pas vérifier en premier que la taille des fichiers est la même, avant de passer ligne par ligne?
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()))
{
}
Il n'y a rien d'étrange à ce sujet. using
est un moyen abrégé de garantir la disposition de l'objet une fois le bloc de code terminé. Si vous avez un objet jetable dans votre bloc externe que le bloc interne doit utiliser, ceci est parfaitement acceptable.
Modifier: la saisie est trop lente pour afficher un exemple de code consolidé. +1 à tous les autres.
Vous pouvez omettre les crochets sur tous les éléments sauf ceux qui se trouvent le plus à l'intérieur:
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 le même usage, comme d'autres l'ont suggéré, mais je suis sûr que beaucoup de gens penseront que c'est déroutant.
Et pour ajouter à la clarté, dans ce cas, puisque chaque instruction suivante 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;
Ceux-ci arrivent de temps en temps quand je code aussi. Vous pourriez envisager de déplacer la deuxième instruction using dans une autre fonction?
De plus, si vous connaissez déjà les chemins, cela ne sert à rien 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 et vérifiera qu’il existe exactement une barre oblique inverse entre le chemin et le nom.
File.OpenText
ouvrira un fichier et créera un StreamReader
en une fois.
En préfixant une chaîne avec @, vous évitez d’échapper à chaque barre oblique inverse (par exemple, @"a\b\c"
)
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(string outFile, string expFile)
{
var outFileBytes = File.ReadAllBytes(outFile);
var expFileBytes = File.ReadAllBytes(expFile);
return (outFileBytes.CalculateCRC16() == expFileBytes.CalculateCRC16());
}
Vous pouvez utiliser la classe intégrée System.Security.Cryptography.MD5, mais le hachage calculé étant un octet [], vous devez donc toujours comparer ces deux tableaux.
Je pense que j'ai peut-être trouvé une façon plus propre syntaxiquement de déclarer cette instruction using, et cela semble fonctionner pour moi? utiliser var comme type dans l'instruction using au lieu de IDisposable semble inférer dynamiquement le 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 à laquelle ils sont attribués, comme dansusing(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
Si vous pouvez attendre C # 8. , vous pouvez utiliser une déclaration utilisant la déclaration .
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);
Son utilisation normale et fonctionne parfaitement. Bien qu'il y ait d'autres moyens de mettre cela en œuvre. Presque chaque réponse est déjà présente 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;
}
}