Zagnieżdżone przy użyciu instrukcji w języku C #


315

Pracuję nad projektem. Muszę porównać zawartość dwóch plików i sprawdzić, czy dokładnie do siebie pasują.

Przed dużą ilością sprawdzania błędów i sprawdzania poprawności mój pierwszy szkic to:

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

Wydaje się, że zagnieżdżanie usinginstrukcji jest trochę dziwne .

Czy jest na to lepszy sposób?


Myślę, że mogłem znaleźć syntaktycznie czystszy sposób na zadeklarowanie tego wyrażenia za pomocą instrukcji, i wydaje się, że działa dla mnie? użycie var jako typu w instrukcji using zamiast IDisposable wydaje się pozwalać na tworzenie instancji obu moich obiektów i wywoływanie ich właściwości i metod klasy, do której są one przypisane, jak przy użyciu (var uow = UnitOfWorkType1 (), uow2 = UnitOfWorkType2 ()) {}
Caleb

Odpowiedzi:


556

Preferowanym sposobem na to jest umieszczenie otwierającego nawiasu klamrowego {po ostatniej usinginstrukcji, jak poniżej:

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

10
Odkurzacz? a także nie zmusza cię do używania tego samego typu. Zawsze robię to w ten sposób, nawet jeśli typy pasują do siebie pod względem czytelności i spójności.
meandmycode

7
@Hardryv: Automatyczny format programu Visual Studio usuwa go. Chodzi o to, aby wyglądać jak lista deklaracji zmiennych.
SLaks,

41
Nie jestem pewien, czy uważam to za bardziej czytelne. Jeśli cokolwiek zepsuje wygląd zagnieżdżonego kodu. I wygląda na to, że pierwsza instrukcja using jest pusta i nieużywana. Ale chyba co kiedykolwiek działa ...: /
Jonathon Watney

10
@Bryan Watts, „contrarians” mogą wyrażać prawdziwe preferencje. Jest bardzo prawdopodobne, że inna grupa deweloperów nie wyraziłaby zgody, gdyby zalecono zagnieżdżanie. Jedynym sposobem na to jest ponowne przeprowadzenie eksperymentu w równoległym wszechświecie.
Dan Rosenstark

6
@fmuecke: To niezbyt prawda; to będzie działać. Reguły IDisposableokreślające, że Dispose()podwójne dzwonienie nie powinny nic robić. Zasada ta dotyczy tylko źle napisanych artykułów jednorazowego użytku.
SLaks,

138

Jeśli obiekty są tego samego typu , możesz wykonać następujące czynności

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

1
Cóż, wszystkie są tego samego typu, jeśli wszystkie są możliwe ID może być obsada?
jpierson

8
@jpierson, który działa, tak, ale wtedy, gdy wywołujesz IDisposableobiekty z wnętrza bloku używającego, nie możemy wywoływać żadnego z członków klasy (bez rzutowania, co pokonuje punkt imo).
Connell,

IDisposable jest typem, więc po prostu użyj go jako typu, aby mieć listę typów mieszanych, jak pokazano w kilku innych odpowiedziach.
Chris Rollins

33

Gdy IDisposablelitery s są tego samego typu, możesz wykonać następujące czynności:

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

Strona MSDN usingzawiera dokumentację dotyczącą tej funkcji języka.

Możesz wykonać następujące czynności, niezależnie od tego, czy IDisposablesą tego samego typu:

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

18

jeśli nie masz nic przeciwko zadeklarowaniu zmiennych dla używanego bloku przed użyciem bloku, możesz zadeklarować je wszystkie w tej samej instrukcji using.

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

W ten sposób xiy są tylko zmiennymi zastępczymi typu ID, których można użyć w bloku używającym, a ty i wewnątrz kodu. Pomyślałem, że wspomnę.


3
Wydaje mi się, że byłoby to mylące dla nowego programisty, który patrzy na twój kod.
Zack

5
To może być zła praktyka; ma efekt uboczny, że zmienne będą istnieć nawet po uwolnieniu niezarządzanych zasobów. Zgodnie z odniesieniem C # firmy Microsoft: „Można utworzyć instancję obiektu zasobu, a następnie przekazać zmienną do instrukcji using, ale nie jest to najlepsza praktyka. W takim przypadku obiekt pozostaje w zasięgu po opuszczeniu przez kontrolę bloku używającego, mimo że będzie prawdopodobnie nie ma już dostępu do jego niezarządzanych zasobów. ”
Robert Altman,

@RobertAltman Masz rację, aw prawdziwym kodzie użyłbym innego podejścia (prawdopodobnie tego z Gavin H.). Jest to po prostu mniej preferowana alternatywa.
Botz3000,

Możesz po prostu przenieść deklaracje w ramach użycia z typecastami. Czy byłoby lepiej?
Timothy Blaisdell,

9

Jeśli chcesz efektywnie porównywać pliki, w ogóle nie używaj StreamReaderów, a wtedy zastosowania nie są konieczne - możesz użyć odczytów strumienia niskiego poziomu, aby pobrać bufory danych do porównania.

Możesz także porównać takie rzeczy, jak rozmiar pliku, aby szybko wykryć różne pliki, aby zaoszczędzić sobie również konieczności odczytu wszystkich danych.


Tak, sprawdzanie rozmiaru pliku to dobry pomysł, oszczędza czas lub odczytuje wszystkie bajty. (+1)
TimothyP

9

Instrukcja using działa poza interfejsem IDisposable, więc inną opcją może być utworzenie pewnego rodzaju klasy złożonej, która implementuje IDisposable i zawiera odniesienia do wszystkich obiektów IDisposable, które normalnie umieszczasz w instrukcji using. Wadą tego jest to, że musisz zadeklarować swoje zmienne najpierw, a poza zakresem, aby były przydatne w bloku używającym wymagającym więcej wierszy kodu, niż wymagałoby to kilka innych sugestii.

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

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

Konstruktor dla DisposableCollection jest w tym przypadku tablicą params, więc możesz podać tyle, ile chcesz.


7

Możesz też powiedzieć:

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

Ale niektórym może być tak trudno czytać. BTW, jako optymalizacja twojego problemu, dlaczego nie sprawdzisz, czy rozmiary plików są najpierw takie same, zanim przejdziesz linia po linii?


6

Można pominąć nawiasy we wszystkich oprócz najbardziej wewnętrznych, używając:

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

Myślę, że jest to czystsze niż stosowanie kilku tego samego typu w tym samym użyciu, jak sugerowali inni, ale jestem pewien, że wiele osób uważa, że ​​jest to mylące


6

Możesz pogrupować wiele obiektów jednorazowych w jedną instrukcję użycia przecinkami:

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

}

5

Nie ma w tym nic dziwnego. usingto skrótowy sposób zapewnienia usunięcia obiektu po zakończeniu bloku kodu. Jeśli masz w swoim zewnętrznym bloku jednorazowy przedmiot, którego musi użyć wewnętrzny blok, jest to całkowicie do przyjęcia.

Edycja: zbyt wolne pisanie na klawiaturze, aby wyświetlić przykład kodu skonsolidowanego. +1 dla wszystkich innych.


5

Aby dodatkowo zwiększyć przejrzystość, w tym przypadku, ponieważ każda kolejna instrukcja jest pojedynczą instrukcją (a nie blokiem), możesz pominąć wszystkie nawiasy:

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;  

Ciekawe rozwiązanie; robienie tego / a nawet użycie 1 zestawu nawiasów na najniższym poziomie może osiągnąć ten sam cel, co układanie ich w lewe wyrównanie (czystsze IMO), przy jednoczesnym spełnieniu pragnienia zagnieżdżenia kosmetyków, o którym wspominali inni, aby wykazać dowolność podporządkowania.
user1172173,

5

Od wersji C # 8.0 możesz używać deklaracji użycia .

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

Spowoduje to usunięcie zmiennych używających na końcu zakresu zmiennych, tj. Na końcu metody.


3

Te pojawiają się od czasu do czasu, kiedy również koduję. Czy możesz rozważyć przeniesienie drugiej instrukcji using do innej funkcji?


3

Czy pytasz również, czy istnieje lepszy sposób na porównanie z plikami? Wolę obliczać CRC lub MD5 dla obu plików i porównywać je.

Na przykład możesz użyć następującej metody rozszerzenia:

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

Gdy już to zrobisz, porównywanie plików jest dość łatwe:

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

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

Możesz użyć wbudowanej klasy System.Security.Cryptography.MD5, ale obliczony skrót jest bajtem [], więc nadal będziesz musiał porównać te dwie tablice.


2
Zamiast brać tablicę bajtów, metoda powinna pobrać Streamobiekt i wywołać ReadBytemetodę, dopóki nie zwróci -1. Pozwoli to zaoszczędzić duże ilości pamięci dla dużych plików.
SLaks,

Jak obliczysz CRC dla wszystkich bajtów?
TimothyP,

Och, nieważne, co powiedziałem: p Thnx, zmienię to w moim kodzie: p Używamy go tylko dla danych <1000 bajtów, więc nie zauważyłem jeszcze problemów, ale i tak się zmieni
TimothyP

Za każdym razem, gdy wywołasz ReadBytepozycję strumienia, przesuwa się o jeden bajt. Dlatego jeśli będziesz go wywoływał, dopóki nie zwróci -1 (EOF), da ci każdy bajt w pliku. msdn.microsoft.com/en-us/library/system.io.stream.readbyte.aspx
SLaks

7
Korzystanie z CRC jest świetne, jeśli chcesz porównywać wiele plików wiele razy, ale w celu pojedynczego porównania musisz odczytać oba pliki w całości, aby obliczyć CRC - Jeśli porównasz dane w małych porcjach, możesz wyjść z porównania jako gdy tylko znajdziesz bajt, który się różni.
Jason Williams,

3

Ponadto, jeśli znasz już ścieżki, skanowanie katalogu nie ma sensu.

Zamiast tego poleciłbym coś takiego:

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 doda folder lub nazwę pliku do ścieżki i upewni się, że między ścieżką a nazwą jest dokładnie jeden ukośnik odwrotny.

File.OpenTextotworzy plik i utworzy go za StreamReaderjednym razem.

Prefiksując ciąg @, możesz uniknąć konieczności ucieczki przed każdym odwrotnym ukośnikiem (np. @"a\b\c")


3

Myślę, że mogłem znaleźć syntaktycznie czystszy sposób na zadeklarowanie tego wyrażenia za pomocą instrukcji, i wydaje się, że działa dla mnie? użycie var jako typu w instrukcji using zamiast IDisposable wydaje się dynamicznie wnioskować o typie obu obiektów i pozwala mi tworzyć instancję obu moich obiektów i wywoływać ich właściwości i metody klasy, do której są przypisane, tak jak w

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

przypadku Jeśli ktoś wie, dlaczego to nie ma racji, proszę dać mi znać


1
Kilka w jednym wierszu działa, jeśli wszystkie rzeczy są tego samego typu. Mieszane typy muszą być podzielone na osobne za pomocą () s. Ale to nie działa z var, musisz podać typ (specyfikacja C # 5, p237)
Chris F Carroll

0

Jest to normalny sposób użytkowania i działa idealnie. Chociaż istnieją inne sposoby realizacji tego. Prawie każda odpowiedź jest już obecna w odpowiedzi na to pytanie. Ale tutaj wymieniam je wszystkie razem.

Już użyte

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

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

Opcja 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;
       }
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.