2009-08-25 12 views
223

Ich arbeite an einem Projekt und muss zwei Dateien vergleichen und sehen, ob sie genau übereinstimmen.Verschachtelt mit Anweisungen in C#

Mein erster Entwurf vor einer Menge Fehlerprüfung und Validierung kam mit:

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); 
    } 
    } 

Es ist ein wenig seltsam scheint Aussagen zu haben verschachtelt werden.

Gibt es einen besseren Weg, dies zu tun?

+2

Wenn eine der Dateien kürzer ist als die andere, aber alle ihre Bytes mit der größeren Datei übereinstimmt der obige Code wird True zurückgeben, obwohl die Dateien nicht genau übereinstimmen –

+0

@Rune FS: Good catch, behoben – SBurris

+0

Ich glaube, ich habe eine syntaktisch sauberere Art und Weise gefunden, dies zu erklären mit der Anweisung, und es scheint für mich zu arbeiten? Die Verwendung von var als Typ in der using-Anweisung anstelle von IDisposable scheint es mir zu ermöglichen, meine beiden Objekte zu instanziieren und ihre Eigenschaften und Methoden der zugeordneten Klasse aufzurufen (var uow = UnitOfWorkType1(), uow2 = UnitOfWorkType2)()) {} – Caleb

Antwort

418

Die bevorzugte Weg, dies zu tun, ist nur eine öffnende Klammer { nach der letzten using Anweisung zu setzen, wie folgt aus:

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

Wow, ich habe heute etwas gelernt. Sie können diesen Ansatz mit einem anderen Typ verwenden. +1 –

+0

Ja, dieser Ansatz ist nett, wenn die Typen unterschiedlich sind. –

+9

Reiniger? und zwingt Sie auch nicht, die gleichen Typen zu verwenden. Ich tue es immer so, auch wenn die Typen für Lesbarkeit und Konsistenz übereinstimmen. – meandmycode

104

Wenn die Objekte des gleichen Typ sind Sie tun können, die folgende

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

Nun, sie sind alle vom selben Typ, wenn sie alle IDisposable sind, vielleicht würde ein Cast funktionieren? – jpierson

+4

@jpierson das funktioniert, ja, aber wenn Sie die IDisposable-Objekte aus dem using-Block heraus aufrufen, können wir keine Klassenmitglieder aufrufen (ohne eine Besetzung, die den Punkt imo besiegt). – Connell

+0

IDisposable ist ein Typ, also verwenden Sie ihn einfach als Typ, um eine Liste gemischter Typen zu erhalten, wie in einigen anderen Antworten zu sehen ist. –

4

Es gibt nichts seltsam darüber. using ist eine Kurzform, um die Entsorgung des Objekts sicherzustellen, sobald der Codeblock fertig ist. Wenn Sie in Ihrem äußeren Block ein Einwegobjekt haben, das der innere Block verwenden muss, ist dies vollkommen akzeptabel.

Bearbeiten: Zu langsam bei der Eingabe, um den konsolidierten Codebeispiel anzuzeigen. +1 für alle anderen.

23

Wenn die IDisposable s vom gleichen Typ sind, können Sie folgendes tun:

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

Die MSDN-Seite auf using Dokumentation zu dieser Sprache-Funktion hat.

können Sie die folgenden Schritte aus, ob die IDisposable s des gleichen Typs sind:

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

Sie können Gruppe mehrere Einweg-Objekte in einer using-Anweisung mit Komma:

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

} 
2

Diese kommen von Zeit zu Zeit, wenn ich auch Code. Sie könnten in Erwägung ziehen, die zweite using-Anweisung in eine andere Funktion zu verschieben?

4

Sie könnten die Klammern auf alle weglassen, aber die am weitesten innen mit:

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; 
    } 
    } 
} 

Ich denke, das ist sauberer als mehrere des gleichen Typs in der gleichen setzen verwenden, wie andere vorgeschlagen haben, aber ich m sicher, dass viele Leute denken, dies verwirrend ist

+3

Sie können alle Klammern in diesem Fall weglassen ... –

6

Sie auch sagen können:

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

Aber einige Leute finden könnte, dass schwer zu lesen. BTW, als eine Optimierung für Ihr Problem, warum überprüfen Sie nicht, dass die Dateigrößen zuerst die gleiche Größe haben, bevor Sie Zeile für Zeile gehen?

2

Wenn Sie die Pfade bereits kennen, ist das Scannen des Verzeichnisses sinnlos.

Stattdessen würde ich so etwas wie dies empfehlen:

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 einen Ordner oder Dateinamen zu einem Pfad hinzufügen und stellen Sie sicher, dass es zwischen dem Pfad und der Namen genau ein Backslash ist.

File.OpenText öffnet eine Datei und erstellen Sie eine StreamReader in einem Rutsch.

durch eine Schnur mit @ prefixing, können Sie vermeiden jede Backslash zu entkommen mit (zB @"a\b\c")

8

Wenn Sie die Dateien effizient vergleichen wollen, tun StreamReaders nicht benutzen, und dann werden die usings aren 't necessary - Sie können Stream-Lesevorgänge auf niedriger Ebene verwenden, um Puffer von zu vergleichenden Daten zu erfassen.

Sie können auch Dinge wie die Dateigröße vergleichen, um schnell verschiedene Dateien zu finden, damit Sie nicht alle Daten lesen müssen.

+0

Ja, die Dateigröße zu überprüfen ist eine gute Idee, spart Ihnen die Zeit oder liest alle Bytes. (+1) – TimothyP

2

Fragen Sie auch, ob es einen besseren Weg gibt, um Dateien zu vergleichen? Ich bevorzuge es, einen CRC oder MD5 für beide Dateien zu berechnen und diese zu vergleichen.

Zum Beispiel könnten Sie die folgende Erweiterung Methode verwenden:

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; 
     } 

Sobald Sie getan haben, dass es ziemlich einfach ist, Dateien zu vergleichen:

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

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

Sie könnten die in System.Security gebaut verwenden .Cryptography.MD5 Klasse, aber der berechnete Hash ist ein Byte [], so dass Sie diese beiden Arrays immer noch vergleichen müssen.

+2

Anstatt ein Byte-Array zu nehmen, sollte die Methode ein "Stream" -Objekt nehmen und die 'ReadByte'-Methode aufrufen, bis sie -1 zurückgibt. Dies spart große Speichermengen für große Dateien. – SLaks

+0

Wie würden Sie dann die CRC über alle Bytes berechnen? – TimothyP

+0

Oh, egal was ich gesagt habe: p Thnx, ich werde das in meinem Code ändern: p Wir verwenden es nur für Daten <1000 Bytes, also habe Probleme noch nicht bemerkt, aber wird sich sowieso ändern – TimothyP

4

Und nur auf die Klarheit hinzuzufügen, in diesem Fall, da jede nachfolgende Anweisung eine einzelne Anweisung ist, (und kein Block), können Sie alle Klammern weglassen:

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; 
+0

Interessante Lösung; Dies zu tun/sogar unter Verwendung der 1 Klammer auf der niedrigsten Ebene, erreicht vielleicht das gleiche Ziel wie das Stapeln von ihnen linksbündig (sauberere IMO), während sie den kosmetischen Nesting-Wunsch anspricht, den andere erwähnt haben, um Unterordnung zu zeigen. – user1172173

13

, wenn Sie don‘ Um die Variablen für Ihren using-Block vor dem using-Block zu deklarieren, können Sie sie alle in derselben using-Anweisung deklarieren.

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

Auf diese Weise, x und y sind nur Platzhalter Variablen vom Typ IDisposable für die Verwendung von Block zu verwenden, und Sie verwenden t und u in Ihrem Code. Ich dachte nur, ich würde es erwähnen.

+0

Ich glaube, dies würde für einen neuen Entwickler verwirrend sein, wenn er Ihren Code betrachtet. – Zack

+0

Dies kann eine schlechte Praxis sein; Es hat einen Nebeneffekt, dass die Variablen auch dann noch existieren, wenn die nicht verwalteten Ressourcen freigegeben wurden. Laut der C# -Referenz von Microsoft "können Sie das Ressourcenobjekt instanziieren und die Variable dann an die using-Anweisung übergeben. Dies ist jedoch keine bewährte Methode. In diesem Fall verbleibt das Objekt im Bereich, nachdem die Kontrolle den using-Block verlassen hat hat wahrscheinlich keinen Zugriff mehr auf seine nicht verwalteten Ressourcen. " –

+0

@RobertAltman Du hast recht, und in echtem Code würde ich einen anderen Ansatz verwenden (wahrscheinlich den von Gavin H). Dies ist nur eine weniger bevorzugte Alternative. – Botz3000

7

Die using-Anweisung arbeitet von der IDisposable-Schnittstelle ab. Eine andere Option könnte also sein, eine Art zusammengesetzte Klasse zu erstellen, die IDisposable implementiert und Referenzen auf alle IDisposable-Objekte hat, die Sie normalerweise in Ihre using-Anweisung einfügen würden. Der Nachteil davon ist, dass Sie Ihre Variablen zuerst und außerhalb des Bereichs deklarieren müssen, damit sie innerhalb des using-Blocks nützlich sind, der mehr Codezeilen erfordert, als einige der anderen Vorschläge erfordern würden.

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

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

Der Konstruktor für DisposableCollection ist in diesem Fall ein Array params, so dass Sie so viele Feeds eingeben können, wie Sie möchten.

2

Ich glaube, ich habe eine syntaktisch sauberere Art und Weise gefunden, dies zu erklären mit der Anweisung, und es scheint für mich zu arbeiten?Die Verwendung von var als Typ in der using-Anweisung anstelle von IDisposable scheint dynamisch auf beide Objekte zu schließen und ermöglicht es mir, beide Objekte zu instanziieren und ihre Eigenschaften und Methoden der zugewiesenen Klasse aufzurufen, wie in

Wenn jemand weiß, warum das nicht stimmt, lass es mich wissen

+1

Mehrere auf einer Zeile funktioniert, wenn alle Dinge vom gleichen Typ sind. Gemischte Typen müssen mit() s getrennt aufgeteilt werden. Aber es funktioniert nicht mit var, Sie müssen einen Typ angeben (C# 5 Spezifikation, p237) –