Annidato usando le istruzioni in C #


316

Sto lavorando a un progetto. Devo confrontare il contenuto di due file e vedere se corrispondono esattamente tra loro.

Prima di molti controlli e convalide degli errori, la mia prima bozza è:

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

Sembra un po 'strano avere usingdichiarazioni annidate .

C'è un modo migliore per farlo?


Penso di aver trovato un modo sintatticamente più pulito di dichiarare questa affermazione usando, e sembra funzionare per me? l'utilizzo di var come tipo nell'istruzione using anziché IDisposable sembra consentirmi di creare un'istanza di entrambi i miei oggetti e di chiamarne le proprietà e i metodi della classe con cui sono allocati, come nell'uso (var uow = UnitOfWorkType1 (), uow2 = UnitOfWorkType2 ()) {}
Caleb,

Risposte:


557

Il modo preferito per farlo è mettere una parentesi graffa aperta solo {dopo l'ultima usingaffermazione, in questo modo:

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

10
Cleaner? e inoltre non ti costringe a usare gli stessi tipi .. Lo faccio sempre in questo modo anche se i tipi corrispondono per leggibilità e coerenza.
meandmycode,

7
@Hardryv: il formato automatico di Visual Studio lo rimuove. L'idea è quella di apparire come un elenco di dichiarazioni variabili.
SLaks

41
Non sono sicuro se lo trovo più leggibile. Semmai rompe l'aspetto del codice nidificato. E sembra che la prima istruzione using sia vuota e inutilizzata. Ma immagino che cosa mai funziona ...: /
Jonathon Watney il

10
@Bryan Watts, i "contrari" potrebbero esprimere vere preferenze. È molto probabile che un diverso gruppo di sviluppatori avrebbe dissentito se fosse stata raccomandata la nidificazione. L'unico modo per sapere è eseguire nuovamente l'esperimento in un universo parallelo.
Dan Rosenstark,

6
@fmuecke: non è molto vero; Funzionerà. Le regole IDisposableaffermano che chiamare Dispose()due volte non dovrebbe fare nulla. Tale regola si applica solo ai prodotti usa e getta scritti male.
SL

138

Se gli oggetti sono dello stesso tipo puoi fare quanto segue

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

1
Beh, sono tutti dello stesso tipo se fossero tutti IDisposable, forse un cast funzionerebbe?
jpierson,

8
@jpierson che funziona, sì, ma quando chiami gli IDisposableoggetti dall'interno del blocco using, non possiamo chiamare nessuno dei membri della classe (senza un cast, che sconfigge il punto imo).
Connell,

IDisposable è un tipo, quindi basta usarlo come tipo per avere un elenco di tipi misti, come si vede in poche altre risposte.
Chris Rollins,

33

Quando le IDisposables sono dello stesso tipo, puoi fare quanto segue:

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

La pagina MSDN su usingcontiene documentazione su questa funzione di lingua.

È possibile effettuare le seguenti operazioni indipendentemente dal fatto che le IDisposables siano dello stesso tipo:

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

18

se non ti dispiace dichiarare le variabili per il blocco using prima del blocco using, puoi dichiararle tutte nella stessa istruzione using.

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

In questo modo, x e y sono solo variabili segnaposto di tipo ID che possono essere utilizzate dal blocco using e si usa te all'interno del codice. Ho pensato di menzionare.


3
Sento che questo potrebbe confondere un nuovo sviluppatore che guarda il tuo codice.
Zack,

5
Questa può essere una cattiva pratica; ha un effetto collaterale che le variabili esisteranno ancora anche dopo che le risorse non gestite sono state liberate. Secondo il riferimento C # di Microsoft, "È possibile creare un'istanza dell'oggetto risorsa e quindi passare la variabile all'istruzione using, ma questa non è una procedura consigliata. In questo caso, l'oggetto rimane nell'ambito dopo che il controllo lascia il blocco using anche se lo farà probabilmente non hanno più accesso alle sue risorse non gestite ".
Robert Altman,

@RobertAltman Hai ragione, e in codice reale userei un altro approccio (probabilmente quello di Gavin H). Questa è solo un'alternativa meno preferibile.
Botz3000,

Potresti semplicemente spostare le dichiarazioni all'interno dell'uso con i typecasts. Sarebbe meglio?
Timothy Blaisdell,

9

Se si desidera confrontare i file in modo efficiente, non utilizzare affatto StreamReader e quindi gli usi non sono necessari: è possibile utilizzare letture di flusso di basso livello per estrarre buffer di dati da confrontare.

Puoi anche confrontare prima cose come la dimensione del file per rilevare rapidamente diversi file per evitare di dover leggere anche tutti i dati.


Sì, controllare la dimensione del file è una buona idea, ti fa risparmiare tempo o leggere tutti i byte. (+1)
Timoteo

9

L'istruzione using funziona al di fuori dell'interfaccia IDisposable, quindi un'altra opzione potrebbe essere quella di creare un tipo di classe composita che implementa IDisposable e abbia riferimenti a tutti gli oggetti IDisposable che normalmente inseriresti nell'istruzione using. Il lato negativo di questo è che devi dichiarare le tue variabili al di fuori e al di fuori dell'ambito affinché siano utili all'interno del blocco using che richiede più righe di codice di quanto richiederebbero alcuni degli altri suggerimenti.

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

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

La funzione di costruzione per DisposableCollection è un array di parametri in questo caso, quindi è possibile inserire tutti gli elementi desiderati.


7

Puoi anche dire:

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

Ma alcune persone potrebbero trovarlo difficile da leggere. A proposito, come ottimizzazione del tuo problema, perché non controlli che le dimensioni del file abbiano le stesse dimensioni prima di procedere riga per riga?


6

Puoi omettere le parentesi su tutti tranne quelli più interni usando:

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

Penso che questo sia più pulito rispetto al mettere molti dello stesso tipo nello stesso uso, come altri hanno suggerito, ma sono sicuro che molte persone penseranno che questo sia confuso


6

Puoi raggruppare più oggetti usa e getta in una dichiarazione using con virgole:

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

}

5

Non c'è niente di strano. usingè un modo abbreviato di garantire lo smaltimento dell'oggetto al termine del blocco di codice. Se hai un oggetto usa e getta nel tuo blocco esterno che il blocco interno deve usare, questo è perfettamente accettabile.

Modifica: troppo lento nella digitazione per mostrare un esempio di codice consolidato. +1 a tutti gli altri.


5

E per aggiungere alla chiarezza, in questo caso, poiché ogni istruzione successiva è una singola istruzione (e non un blocco), puoi omettere tutte le parentesi:

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;  

Soluzione interessante; fare questo / anche usando il 1 set di parentesi al livello più basso forse, raggiunge lo stesso obiettivo di impilarli giustificati a sinistra (IMO più pulito), affrontando il desiderio di nidificazione cosmetica che gli altri menzionati mostrano qualsiasi subordinazione.
user1172173

5

Da C # 8.0 è possibile utilizzare una dichiarazione using .

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

Questo eliminerà le variabili utilizzanti alla fine dell'ambito delle variabili, cioè alla fine del metodo.


3

Questi vengono di volta in volta anche quando scrivo codice. Potresti considerare di spostare la seconda istruzione using in un'altra funzione?


3

Stai anche chiedendo se esiste un modo migliore per confrontare i file? Preferisco calcolare un CRC o MD5 per entrambi i file e confrontarli.

Ad esempio, è possibile utilizzare il seguente metodo di estensione:

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

Una volta fatto, è abbastanza facile confrontare i file:

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

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

È possibile utilizzare la classe System.Security.Cryptography.MD5 integrata, ma l'hash calcolato è un byte [], quindi è comunque necessario confrontare questi due array.


2
Invece di prendere un array di byte, il metodo dovrebbe prendere un Streamoggetto e chiamarlo ReadBytefino a quando non restituisce -1. Ciò consentirà di risparmiare grandi quantità di memoria per file di grandi dimensioni.
SLaks

Come calcoleresti quindi il crc su tutti i byte?
TimothyP,

Oh, non importa quello che ho detto: p Thnx, lo cambierò nel mio codice: p Lo usiamo solo per dati <1000 byte, quindi non ho ancora notato problemi ma cambieranno comunque
TimothyP

Ogni volta che chiami ReadBytela posizione del flusso avanza di un byte. Pertanto, se continui a chiamarlo fino a quando non restituisce -1 (EOF), ti darà ogni byte nel file. msdn.microsoft.com/en-us/library/system.io.stream.readbyte.aspx
SLaks

7
L'uso di un CRC è ottimo se si desidera confrontare più file più volte, ma per un singolo confronto è necessario leggere entrambi i file nella loro interezza per calcolare i CRC - Se si confrontano i dati in piccoli blocchi, è possibile uscire dal confronto come non appena trovi un byte che differisce.
Jason Williams,

3

Inoltre, se conosci già i percorsi, non ha senso scansionare la directory.

Invece, consiglierei qualcosa del genere:

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 aggiungerà una cartella o un nome file a un percorso e si assicurerà che ci sia esattamente una barra rovesciata tra il percorso e il nome.

File.OpenTextaprirà un file e creerà uno StreamReaderin una volta sola.

Con il prefisso di una stringa con @, puoi evitare di dover sfuggire a tutte le barre rovesciate (ad es. @"a\b\c")


3

Penso di aver trovato un modo sintatticamente più pulito di dichiarare questa affermazione usando, e sembra funzionare per me? usare var come tipo nell'istruzione using anziché IDisposable sembra inferire dinamicamente il tipo su entrambi gli oggetti e mi permette di creare un'istanza di entrambi i miei oggetti e di chiamarne le proprietà e i metodi della classe con cui sono allocati, come in

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

Se qualcuno sa perché questo non è giusto, per favore fatemi sapere


1
Diversi su una riga funzionano se tutte le cose sono dello stesso tipo. I tipi misti devono essere divisi su separati usando () s. Ma non funziona con var, devi specificare un tipo (specifica C # 5, p237)
Chris F Carroll,

0

È il normale modo di utilizzo e funziona perfettamente. Sebbene ci siano altri modi per implementarlo. Quasi ogni risposta è già presente nella risposta a questa domanda. Ma qui li sto elencando tutti insieme.

Già usato

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

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

opzione 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;
       }
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.