Verschachtelt mit Anweisungen in C #


315

Ich arbeite an einem Projekt. Ich muss den Inhalt von zwei Dateien vergleichen und sehen, ob sie genau zueinander passen.

Vor vielen Fehlerprüfungen und -validierungen ist mein erster Entwurf:

  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 scheint ein wenig seltsam, verschachtelt zu haben using Anweisungen .

Gibt es einen besseren Weg, dies zu tun?


Ich glaube, ich habe einen syntaktisch saubereren Weg gefunden, dies mit einer Anweisung zu deklarieren, und es scheint für mich zu funktionieren? 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 Klasse aufzurufen, der sie zugewiesen sind, wie bei using (var uow = UnitOfWorkType1 (), uow2 = UnitOfWorkType2 ()) {}
Caleb

Antworten:


556

Der bevorzugte Weg, dies zu tun, besteht darin, erst {nach der letzten usingAnweisung eine öffnende Klammer wie folgt zu setzen :

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

10
Reiniger? und zwingt Sie auch nicht dazu, dieselben Typen zu verwenden. Ich mache es immer so, auch wenn die Typen hinsichtlich Lesbarkeit und Konsistenz übereinstimmen.
Meandmycode

7
@Hardryv: Das automatische Format von Visual Studio entfernt es. Die Idee ist, wie eine Liste von Variablendeklarationen auszusehen.
SLaks

41
Ich bin mir nicht sicher, ob ich das überhaupt besser lesbar finde. Wenn überhaupt, bricht es das Aussehen von verschachteltem Code. Und es sieht so aus, als ob die erste using-Anweisung leer und unbenutzt ist. Aber ich denke, was auch immer funktioniert ...: /
Jonathon Watney

10
@ Bryan Watts, die "Contrarians" können echte Vorlieben ausdrücken. Es ist sehr wahrscheinlich, dass eine andere Gruppe von Entwicklern anderer Meinung gewesen wäre, wenn eine Verschachtelung empfohlen worden wäre. Die einzige Möglichkeit, dies zu wissen, besteht darin, das Experiment erneut in einem Paralleluniversum durchzuführen.
Dan Rosenstark

6
@fmuecke: Das ist nicht sehr wahr; es wird klappen. Die Regeln für IDisposabledas Dispose()zweimalige Aufrufen sollten nichts bewirken. Diese Regel gilt nur für schlecht geschriebene Einwegartikel.
SLaks

138

Wenn die Objekte vom gleichen Typ sind , können Sie Folgendes tun

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

1
Nun, sie sind alle vom gleichen Typ, wenn sie alle identifizierbar sind. Vielleicht würde eine Besetzung funktionieren?
Jpierson

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

IDisposable ist ein Typ. Verwenden Sie ihn daher einfach als Typ, um eine Liste gemischter Typen zu erstellen, wie in einigen anderen Antworten dargestellt.
Chris Rollins

33

Wenn die IDisposables 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 eine Dokumentation zu dieser Sprachfunktion.

Sie können Folgendes tun, unabhängig davon, ob die IDisposables vom gleichen Typ sind oder nicht :

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

18

Wenn es Ihnen nichts ausmacht, 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 sind x und y nur Platzhaltervariablen vom Typ IDisposable für den using-Block, und Sie verwenden t und u in Ihrem Code. Ich dachte nur, ich würde es erwähnen.


3
Ich denke, dies wäre verwirrend für einen neuen Entwickler, der sich Ihren Code ansieht.
Zack

5
Dies kann eine schlechte Praxis sein; Dies hat den Nebeneffekt, dass die Variablen auch nach der Freigabe der nicht verwalteten Ressourcen weiterhin vorhanden sind. In der C # -Referenz von Microsoft heißt es: "Sie können das Ressourcenobjekt instanziieren und dann die Variable an die using-Anweisung übergeben. Dies ist jedoch keine bewährte Methode. In diesem Fall bleibt das Objekt im Gültigkeitsbereich, nachdem die Steuerung den using-Block verlassen hat, obwohl dies der Fall ist." haben wahrscheinlich keinen Zugriff mehr auf nicht verwaltete Ressourcen. "
Robert Altman

@ RobertAltman Sie haben 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

Sie können die Deklarationen einfach mit Typecasts in die using verschieben. Wäre das besser?
Timothy Blaisdell

9

Wenn Sie die Dateien effizient vergleichen möchten, verwenden Sie StreamReaders überhaupt nicht. Die Verwendung ist dann nicht erforderlich. Sie können Stream-Lesevorgänge auf niedriger Ebene verwenden, um Datenpuffer zum Vergleichen abzurufen.

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


Ja, das Überprüfen der Dateigröße ist eine gute Idee, spart Ihnen Zeit oder das Lesen aller Bytes. (+1)
TimothyP

9

Die using-Anweisung funktioniert über die IDisposable-Schnittstelle. Eine andere Option könnte darin bestehen, eine Art zusammengesetzte Klasse zu erstellen, die IDisposable implementiert und Verweise auf alle IDisposable-Objekte enthält, die Sie normalerweise in Ihre using-Anweisung einfügen würden. Der Nachteil dabei ist, dass Sie Ihre Variablen zuerst und außerhalb des Gültigkeitsbereichs 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 params-Array, sodass Sie so viele eingeben können, wie Sie möchten.


7

Du kannst auch sagen:

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

Aber manche Leute finden das vielleicht schwer zu lesen. Übrigens, als Optimierung für Ihr Problem, warum überprüfen Sie nicht zuerst, ob die Dateigrößen gleich groß sind, bevor Sie Zeile für Zeile fortfahren?


6

Sie können die Klammern bei allen außer den innersten weglassen, indem Sie Folgendes verwenden:

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 die gleiche Verwendung zu setzen, wie andere vorgeschlagen haben, aber ich bin sicher, dass viele Leute denken werden, dass dies verwirrend ist


6

Sie können mehrere Einwegobjekte in einer using-Anweisung mit Kommas gruppieren:

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

}

5

Es ist nichts Seltsames daran. usingist eine Kurzform, um die Entsorgung des Objekts nach Abschluss des Codeblocks sicherzustellen. Wenn sich in Ihrem äußeren Block ein Einwegobjekt befindet, das der innere Block verwenden muss, ist dies durchaus akzeptabel.

Bearbeiten: Die Eingabe ist zu langsam, um ein Beispiel für konsolidierten Code anzuzeigen. +1 an alle anderen.


5

Und um die Klarheit zu erhöhen, können Sie in diesem Fall alle Klammern weglassen, da jede aufeinanderfolgende Anweisung eine einzelne Anweisung (und kein Block) ist:

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;  

Interessante Lösung; Wenn Sie dies tun / sogar die 1 Klammern auf der untersten Ebene verwenden, wird möglicherweise das gleiche Ziel erreicht, wie wenn Sie sie linksbündig stapeln (sauberere IMO), während Sie gleichzeitig den kosmetischen Verschachtelungswunsch ansprechen, den andere erwähnt haben, um eine Unterordnung zu zeigen.
user1172173

5

Seit C # 8.0 können Sie eine using-Deklaration verwenden .

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

Dadurch werden die verwendenden Variablen am Ende des Gültigkeitsbereichs der Variablen, dh am Ende der Methode, entsorgt.


3

Diese kommen von Zeit zu Zeit vor, wenn ich auch codiere. Sie könnten erwägen, die zweite using-Anweisung in eine andere Funktion zu verschieben?


3

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

Beispielsweise könnten Sie die folgende Erweiterungsmethode 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 dies getan haben, ist es ziemlich einfach, 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önnen die integrierte System.Security.Cryptography.MD5-Klasse verwenden, aber der berechnete Hash ist ein Byte [], sodass Sie diese beiden Arrays noch vergleichen müssen.


2
Anstatt ein Byte-Array zu verwenden, sollte die Methode ein StreamObjekt nehmen und die ReadByteMethode aufrufen , bis sie -1 zurückgibt. Dies spart viel Speicher für große Dateien.
SLaks

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

Oh, egal was ich gesagt habe: p Danke, ich werde das in meinem Code ändern: p Wir verwenden es nur für Daten <1000 Bytes, haben also noch keine Probleme bemerkt, werden uns aber trotzdem ändern
TimothyP

Jedes Mal, wenn Sie ReadBytedie Position des Streams aufrufen, wird um ein Byte vorgerückt. Wenn Sie es daher so lange aufrufen, bis es -1 (EOF) zurückgibt, erhalten Sie jedes Byte in der Datei. msdn.microsoft.com/en-us/library/system.io.stream.readbyte.aspx
SLaks

7
Die Verwendung eines CRC ist ideal, wenn Sie mehrere Dateien mehrmals vergleichen möchten. Für einen einzelnen Vergleich müssen Sie jedoch beide Dateien vollständig lesen, um die CRCs zu berechnen. Wenn Sie die Daten in kleinen Blöcken vergleichen, können Sie den Vergleich als beenden sobald Sie ein Byte finden, das sich unterscheidet.
Jason Williams

3

Wenn Sie die Pfade bereits kennen, macht es keinen Sinn, das Verzeichnis zu scannen.

Stattdessen würde ich so etwas 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 Fügt einem Pfad einen Ordner oder Dateinamen hinzu und stellt sicher, dass zwischen dem Pfad und dem Namen genau ein Backslash steht.

File.OpenText öffnet eine Datei und erstellt eine StreamReader auf einmal.

Indem Sie einer Zeichenfolge @ voranstellen, müssen Sie vermeiden, dass Sie jedem Backslash entkommen müssen (z. B. @"a\b\c").


3

Ich glaube, ich habe einen syntaktisch saubereren Weg gefunden, dies mit einer Anweisung zu deklarieren, und es scheint für mich zu funktionieren? Die Verwendung von var als Typ in der using-Anweisung anstelle von IDisposable scheint den Typ für beide Objekte dynamisch abzuleiten und ermöglicht es mir, beide Objekte zu instanziieren und ihre Eigenschaften und Methoden für die Klasse aufzurufen, der sie zugewiesen sind, wie in

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

Wenn jemand weiß, warum dies so ist ist nicht richtig, bitte lass es mich wissen


1
Mehrere in einer Zeile funktionieren, wenn alle Dinge vom gleichen Typ sind. Gemischte Typen müssen mit () s über separate Typen aufgeteilt werden. Aber es funktioniert nicht mit var, Sie müssen einen Typ angeben (C # 5-Spezifikation, S. 237)
Chris F Carroll

0

Es ist die normale Art der Verwendung und funktioniert perfekt. Es gibt zwar einige andere Möglichkeiten, dies umzusetzen. Fast jede Antwort ist bereits in der Antwort dieser Frage enthalten. Aber hier liste ich alle zusammen auf.

Schon benutzt

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;
       }
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.