Anidado utilizando declaraciones en C #


316

Estoy trabajando en un proyecto Tengo que comparar el contenido de dos archivos y ver si coinciden con precisión.

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

Parece un poco extraño tener usingdeclaraciones anidadas .

¿Hay una mejor manera de hacer esto?


Creo que puedo haber encontrado una forma sintácticamente más limpia de declarar esto usando una declaración, y parece que funciona para mí. El uso de var como su tipo en la instrucción de uso en lugar de IDisposable parece permitirme crear una instancia de mis dos objetos y llamar a sus propiedades y métodos de la clase con la que están asignados, como al usar (var uow = UnitOfWorkType1 (), uow2 = UnitOfWorkType2 ()) {}
Caleb

Respuestas:


557

La forma preferida de hacer esto es poner una llave de apertura {después de la última usingdeclaración, de esta manera:

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

10
¿Limpiador? y tampoco te obliga a usar los mismos tipos. Siempre lo hago de esta manera, incluso si los tipos coinciden con la legibilidad y la coherencia.
meandmycode

77
@Hardryv: el formato automático de Visual Studio lo elimina. La idea es parecer una lista de declaraciones de variables.
SLaks

41
No estoy seguro si me parece más legible. En todo caso, rompe el aspecto del código anidado. Y parece que la primera declaración que usa está vacía y sin usar. Pero, supongo que lo que funciona ...: /
Jonathon Watney el

10
@Bryan Watts, los "contrarios" pueden estar expresando preferencias reales. Es muy probable que un grupo diferente de desarrolladores hubiera disentido si se hubiera recomendado anidar. La única forma de saber es volver a ejecutar el experimento en un universo paralelo.
Dan Rosenstark

66
@fmuecke: Eso no es muy cierto; funcionará. Las reglas para IDisposableestablecer que llamar Dispose()dos veces no debe hacer nada. Esa regla es solo en caso de desechables mal escritos.
SLaks

138

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

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

1
Bueno, todos son del mismo tipo si todos son desechables, ¿tal vez un elenco funcionaría?
jpierson

8
@jpierson que sí funciona, sí, pero cuando estás llamando a los IDisposableobjetos desde dentro del bloque de uso, no podemos llamar a ninguno de los miembros de la clase (sin un yeso, lo que vence el punto imo).
Connell

IDisposable es un tipo, así que úselo como tipo para tener una lista de tipos mixtos, como se ve en algunas otras respuestas.
Chris Rollins

33

Cuando los IDisposables 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 usingtiene documentación sobre esta función de idioma.

Puede hacer lo siguiente si los IDisposables son o no del mismo tipo:

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

18

si no le importa declarar las variables para su bloque de uso antes del bloque de uso, puede declararlas todas en la misma declaración de uso.

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

De esa manera, x e y son solo variables de marcador de posición de tipo ID disponibles para que las use el bloque y usted usa tyu dentro de su código. Solo pensé en mencionarlo.


3
Siento que esto sería confuso para un nuevo desarrollador que mira su código.
Zack

55
Esto puede ser una mala práctica; tiene un efecto secundario de que las variables seguirán existiendo incluso después de que los recursos no administrados hayan sido liberados. Según la referencia de C # de Microsoft, "puede crear una instancia del objeto de recurso y luego pasar la variable a la instrucción de uso, pero esta no es una práctica recomendada. En este caso, el objeto permanece dentro del alcance después de que el control abandone el bloque de uso, aunque lo hará probablemente ya no tenga acceso a sus recursos no administrados ".
Robert Altman

@RobertAltman Tienes razón, y en código real usaría otro enfoque (probablemente el de Gavin H). Esta es solo una alternativa menos preferible.
Botz3000

Simplemente puede mover las declaraciones dentro del uso con typecasts. ¿Sería eso mejor?
Timothy Blaisdell

9

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 buffers de datos para comparar.

También puede comparar cosas como el tamaño del archivo primero para detectar rápidamente diferentes archivos para evitar tener que leer todos los datos.


Sí, verificar el tamaño del archivo es una buena idea, le ahorra tiempo o lee todos los bytes. (+1)
TimothyP

9

La declaración de uso 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 declaración de uso. El lado negativo de esto es que debe declarar sus variables primero y fuera del alcance para que sean útiles dentro del bloque de uso que requiere más líneas de código que algunas de las otras sugerencias requerirían.

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

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

El constructor de DisposableCollection es una matriz de parámetros en este caso, por lo que puede alimentar tantos como desee.


7

También puede decir:

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

Pero algunas personas pueden encontrar eso difícil de leer. Por cierto, como una optimización de su problema, ¿por qué no comprueba que los tamaños de archivo son del mismo tamaño primero, antes de ir línea por línea?


6

Puede omitir los corchetes en todos menos en el más interno 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;
    }
  }
}

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


6

Puede agrupar varios objetos desechables en una instrucción de uso con comas:

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

}

5

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

Editar: Demasiado lento al escribir para mostrar un ejemplo de código consolidado. +1 a todos los demás.


5

Y para agregar más claridad, en este caso, dado que cada declaración sucesiva es una declaración ú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;  

Solución interesante hacer esto / incluso usar el conjunto de 1 paréntesis en el nivel más bajo tal vez, logra el mismo objetivo que apilarlos justificados a la izquierda (IMO más limpia), al tiempo que aborda el deseo de anidación cosmético que otros mencionaron para mostrar cualquier subordinación.
user1172173

5

Desde C # 8.0 puede usar una declaración de uso .

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

Esto eliminará las variables de uso al final del alcance de las variables, es decir, al final del método.


3

Estos surgen de vez en cuando cuando codifico también. ¿Podría considerar mover el segundo uso de la declaración a otra función?


3

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

Por ejemplo, podría 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 integrada System.Security.Cryptography.MD5, pero el hash calculado es un byte [], por lo que aún tendría que comparar esas dos matrices.


2
En lugar de tomar una matriz de bytes, el método debe tomar un Streamobjeto y llamar al ReadBytemétodo hasta que devuelva -1. Esto ahorrará grandes cantidades de memoria para archivos grandes.
SLaks

¿Cómo calcularías entonces el crc sobre todos los bytes?
TimothyP

Oh, no importa lo que dije: p Thnx, voy a cambiar eso en mi código: p Nosotros sólo lo uso para los datos <1000 bytes por lo que tienen problemas no cuenta todavía, pero va a cambiar de todos modos
TimothyP

Cada vez que llama a ReadBytela posición de la transmisión, avanza un byte. Por lo tanto, si sigue llamándolo hasta que devuelva -1 (EOF), le dará cada byte en el archivo. msdn.microsoft.com/en-us/library/system.io.stream.readbyte.aspx
SLaks

77
El uso de un CRC es excelente si desea comparar varios archivos varias veces, pero para una comparación única debe leer ambos archivos en su totalidad para calcular los CRC: si compara los datos en pequeños fragmentos, puede salir de la comparación como tan pronto como encuentre un byte que difiera.
Jason Williams el

3

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 uno de una vez.

Al prefijar una cadena con @, puede evitar tener que escapar de cada barra diagonal inversa (por ejemplo, @"a\b\c")


3

Creo que puedo haber encontrado una forma sintácticamente más limpia de declarar esto usando una declaración, y parece que funciona para mí. El uso de var como su tipo en la instrucción de uso en lugar de IDisposable parece inferir dinámicamente el tipo en ambos objetos y me permite crear una instancia de mis dos objetos y llamar a 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 avísame


1
Varios en una línea funcionan si todas las cosas son del mismo tipo. Los tipos mixtos tienen que dividirse por separado usando () s. Pero no funciona con var, debe especificar un tipo (especificación C # 5, p237)
Chris F Carroll

0

Es la forma normal de uso y funciona perfecto. Aunque hay otras formas de implementar esto. Casi todas las respuestas ya están presentes en la respuesta de esta pregunta. Pero aquí los enumero a todos juntos.

Ya usado

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

Opción 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;
      }
    }
  }

opcion 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;
       }
    }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.