Anidado usando declaraciones en C #

Estoy trabajando en un proyecto. Tengo que comparar el contenido de dos archivos y ver si coinciden exactamente.

Antes de una gran cantidad de verificación de errores y validación, mi primer borrador es:

DirectoryInfo di = new DirectoryInfo(Environment.CurrentDirectory + "\\TestArea\\"); FileInfo[] files = di.GetFiles(filename + ".*"); FileInfo outputFile = files.Where(f => f.Extension == ".out").Single(); FileInfo expectedFile = files.Where(f => f.Extension == ".exp").Single (); 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); } } 

Parece un poco extraño haber nested using declaraciones.

¿Hay una mejor manera de hacer esto?

La forma preferida de hacerlo es colocar solo una llave de apertura { después de la última instrucción de using , como esta:

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

Si los objetos son del mismo tipo , puede hacer lo siguiente

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

Cuando los IDisposable s son del mismo tipo, puede hacer lo siguiente:

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

La página de MSDN sobre el using tiene documentación sobre esta función de idioma.

Puede hacer lo siguiente ya sea que los IDisposable sean del mismo tipo o no:

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

si no te importa declarar las variables para tu bloque de uso antes del bloque de uso, puedes declararlas todas en la misma instrucción de uso.

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

De esta forma, xey son solo variables de marcador de posición de tipo IDisposable para usar en el bloque y usted usa t y u dentro de su código. Solo pensé en mencionarlo.

Si desea comparar los archivos de manera eficiente, no use StreamReaders en absoluto, y luego los usos no son necesarios; puede usar lecturas de flujo de bajo nivel para extraer los buffers de datos para comparar.

También puede comparar cosas como el tamaño del archivo primero para detectar rápidamente diferentes archivos y ahorrarse la necesidad de leer todos los datos también.

La sentencia using funciona fuera de la interfaz IDisposable, por lo que otra opción podría ser crear algún tipo de clase compuesta que implemente IDisposable y tenga referencias a todos los objetos IDisposable que normalmente pondría en su statement de uso. La desventaja de esto es que debe declarar sus variables primero y fuera del scope para que sean útiles dentro del bloque de uso que requiera más líneas de código de las que requerirían algunas de las otras sugerencias.

 Connection c = new ...; Transaction t = new ...; using (new DisposableCollection(c, t)) { ... } 

El constructor de DisposableCollection es una matriz params en este caso para que pueda alimentar tantas como quiera.

También puede decir:

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

Pero algunas personas pueden encontrarlo difícil de leer. Por cierto, como una optimización para su problema, ¿por qué no verifica que los tamaños de los archivos sean del mismo tamaño primero, antes de ir línea por línea?

Podrías omitir los corchetes en todos menos en el interno utilizando:

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

Creo que esto es más limpio que poner varios del mismo tipo en el mismo uso, como otros han sugerido, pero estoy seguro de que mucha gente pensará que esto es confuso.

No hay nada de extraño en eso. using es una forma abreviada de garantizar la eliminación del objeto una vez que el bloque de código ha finalizado. Si tiene un objeto desechable en su bloque externo que necesita usar el bloque interno, esto es perfectamente aceptable.

Editar: demasiado lento en la escritura para mostrar el ejemplo de código consolidado. +1 a todos los demás.

Y para agregar más claridad, en este caso, dado que cada statement sucesiva es una statement única (y no un bloque), puede omitir todos los corchetes:

 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; 

Puede agrupar múltiples objetos desechables en una statement using con comas:

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

Estos surgen de vez en cuando cuando codigo también. ¿Podría considerar mover la segunda statement de uso a otra función?

¿También preguntas si hay una mejor manera de comparar los archivos? Prefiero calcular un CRC o MD5 para ambos archivos y compararlos.

Por ejemplo, puede usar el siguiente método de extensión:

 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 vez que haya hecho eso, es bastante fácil comparar archivos:

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

Podría usar la clase incorporada System.Security.Cryptography.MD5, pero el hash calculado es un byte [], por lo que aún tendría que comparar esas dos matrices.

Además, si ya conoce las rutas, no tiene sentido escanear el directorio.

En cambio, recomendaría algo como esto:

 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 agregará una carpeta o nombre de archivo a una ruta y se asegurará de que haya exactamente una barra invertida entre la ruta y el nombre.

File.OpenText abrirá un archivo y creará un StreamReader de una vez.

Al anteponer una cadena con @, puede evitar tener que escapar cada barra invertida (p. Ej., @"a\b\c" )

Creo que puedo haber encontrado una manera sintácticamente más clara de declarar esta statement de uso, y parece funcionar para mí. usar var como su tipo en la instrucción using en lugar de IDisposable parece inferir dinámicamente el tipo en ambos objetos y me permite crear una instancia de ambos objetos y llamar sus propiedades y métodos de la clase con la que están asignados, como en

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

Si alguien sabe por qué esto no está bien, por favor, hágamelo saber