Aninhado usando instruções em C #


316

Estou trabalhando em um projeto. Eu tenho que comparar o conteúdo de dois arquivos e ver se eles se combinam com precisão.

Antes de muita verificação e validação de erros, meu primeiro rascunho é:

  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 um pouco estranho ter usingdeclarações aninhadas .

Existe uma maneira melhor de fazer isso?


Eu acho que posso ter encontrado uma maneira sintaticamente mais limpa de declarar isso usando declaração, e parece funcionar para mim? usar var como seu tipo na instrução using em vez de IDisposable parece permitir que eu instancie meus objetos e chame suas propriedades e métodos da classe com a qual eles estão alocados, como no uso (var uow = UnitOfWorkType1 (), uow2 = UnitOfWorkType2 ()) {}
Caleb

Respostas:


557

A maneira preferida de fazer isso é colocar apenas uma chave de abertura {após a última usingdeclaração, assim:

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

10
Limpador? e também não força você a usar os mesmos tipos. Eu sempre faço dessa maneira, mesmo que os tipos correspondam à legibilidade e consistência.
meandmycode

7
@ Hardryv: o formato automático do Visual Studio o remove. A idéia é parecer uma lista de declarações de variáveis.
SLaks

41
Não tenho certeza se acho isso mais legível. Se alguma coisa quebra a aparência do código aninhado. E parece que a primeira instrução using está vazia e sem uso. Mas, eu acho que nunca funciona ...: /
Jonathon Watney

10
@Bryan Watts, os "contrários" podem estar expressando preferências reais. É muito provável que um grupo diferente de desenvolvedores tenha discordado se o aninhamento fosse recomendado. A única maneira de saber é executar o experimento novamente em um universo paralelo.
Dan Rosenstark

6
@ fmuecke: Isso não é muito verdade; vai funcionar. As regras para IDisposableafirmar que chamar Dispose()duas vezes não deve fazer nada. Essa regra é apenas no caso de descartáveis ​​mal escritos.
SLaks

138

Se os objetos forem do mesmo tipo, você pode fazer o seguinte

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

1
Bem, eles são todos do mesmo tipo, se são todos descartáveis, talvez um elenco funcione?
jpierson

8
@jpierson que funciona, sim, mas quando você está chamando os IDisposableobjetos de dentro do bloco using, não podemos chamar nenhum membro da classe (sem uma conversão, o que derrota o ponto).
22413 Connell

IDisposable é um tipo, basta usá-lo como o tipo para ter uma lista de tipos mistos, como visto em algumas outras respostas.
Chris Rollins

33

Quando os IDisposablesão do mesmo tipo, você pode fazer o seguinte:

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

A página do MSDN on usingtem documentação sobre esse recurso de idioma.

Você pode fazer o seguinte, se os IDisposables são ou não do mesmo tipo:

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

18

se você não se importa em declarar as variáveis ​​para o seu bloco using antes do bloco using, você pode declarar todas elas na mesma instrução using.

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

Dessa forma, xey são apenas variáveis ​​de espaço reservado do tipo IDisponíveis para o bloco using e você usa t e u dentro do seu código. Só pensei em mencionar.


3
Eu sinto que isso seria confuso para um novo desenvolvedor olhando para o seu código.
Zack

5
Isso pode ser uma má prática; tem um efeito colateral que as variáveis ​​ainda existirão mesmo após a liberação dos recursos não gerenciados. De acordo com a referência de C # da Microsoft, "Você pode instanciar o objeto de recurso e passar a variável para a instrução using, mas essa não é uma prática recomendada. Nesse caso, o objeto permanece no escopo após o controle deixar o bloco using, mesmo que provavelmente não terá mais acesso a seus recursos não gerenciados ".
Robert Altman

@RobertAltman Você está certo, e em código real eu usaria outra abordagem (provavelmente a de Gavin H). Esta é apenas uma alternativa menos preferível.
Botz3000

Você pode simplesmente mover as declarações para dentro do usando com tipografias. Isso seria melhor?
Timothy Blaisdell

9

Se você deseja comparar os arquivos com eficiência, não use StreamReaders e, em seguida, os usos não são necessários - você pode usar leituras de fluxo de baixo nível para extrair buffers de dados para comparar.

Você também pode comparar itens como o tamanho do arquivo primeiro para detectar rapidamente arquivos diferentes e evitar a necessidade de ler todos os dados.


Sim, verificar o tamanho do arquivo é uma boa ideia, economiza tempo ou lê todos os bytes. (+1)
TimothyP

9

A instrução using funciona fora da interface IDisposable, portanto, outra opção poderia ser criar algum tipo de classe composta que implemente IDisposable e tenha referências a todos os objetos IDisposable que você normalmente colocaria na instrução using. O lado negativo disso é que você deve declarar suas variáveis ​​primeiro e fora do escopo para que sejam úteis dentro do bloco using, exigindo mais linhas de código do que algumas das outras sugestões exigiriam.

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

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

O construtor para DisposableCollection é uma matriz de parâmetros neste caso, para que você possa alimentar quantas quiser.


7

Você também pode dizer:

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

Mas algumas pessoas podem achar isso difícil de ler. BTW, como uma otimização para o seu problema, por que você não verifica se os tamanhos dos arquivos são do mesmo tamanho antes de ir linha por linha?


6

Você pode omitir os colchetes em todos, exceto os mais internos, 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;
    }
  }
}

Eu acho que isso é mais limpo do que colocar vários do mesmo tipo no mesmo uso, como outros sugeriram, mas tenho certeza que muitas pessoas acharão isso confuso


6

Você pode agrupar vários objetos descartáveis ​​em uma instrução using com vírgulas:

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

}

5

Não há nada de estranho nisso. usingé uma maneira abreviada de garantir o descarte do objeto após a conclusão do bloco de código. Se você tem um objeto descartável no bloco externo que o bloco interno precisa usar, isso é perfeitamente aceitável.

Editar: muito lenta na digitação para mostrar o exemplo de código consolidado. +1 para todos os outros.


5

E, para aumentar a clareza, neste caso, como cada instrução sucessiva é uma única instrução (e não um bloco), você pode omitir todos os colchetes:

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;  

Solução interessante; fazer isso / mesmo usando o conjunto de 1 colchetes no nível mais baixo talvez, atinja o mesmo objetivo de empilhá-los com justificação para a esquerda (IMO mais limpo), enquanto atende ao desejo de aninhamento cosmético que outros mencionados mencionam mostrar qualquer subordinação.
user1172173

5

Desde o C # 8.0, você pode usar uma declaração 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);

Isso descartará as variáveis ​​em uso no final do escopo das variáveis, ou seja, no final do método.


3

Eles surgem de tempos em tempos quando eu codigo também. Você pode considerar mover a segunda instrução usando para outra função?


3

Você também está perguntando se existe uma maneira melhor de comparar com arquivos? Prefiro calcular um CRC ou MD5 para os dois arquivos e compará-los.

Por exemplo, você pode usar o seguinte método de extensão:

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

Depois de fazer isso, é muito fácil comparar arquivos:

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

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

Você pode usar a classe System.Security.Cryptography.MD5 embutida, mas o hash calculado é um byte [], portanto você ainda precisará comparar essas duas matrizes.


2
Em vez de usar uma matriz de bytes, o método deve pegar um Streamobjeto e chamar o ReadBytemétodo até retornar -1. Isso economizará grandes quantidades de memória para arquivos grandes.
SLaks

Como você calcularia o CRC sobre todos os bytes?
TimothyP

Oh não importa o que eu disse: p Thnx, eu vou mudar isso no meu código: p Nós só usá-lo para dados <1000 bytes por isso têm problemas não notado ainda, mas vai mudar de qualquer maneira
TimothyP

Toda vez que você chama ReadBytea posição do fluxo, avança em um byte. Portanto, se você continuar chamando até retornar -1 (EOF), ele fornecerá todos os bytes do arquivo. msdn.microsoft.com/en-us/library/system.io.stream.readbyte.aspx
SLaks

7
O uso de um CRC é ótimo se você deseja comparar vários arquivos várias vezes, mas para uma única comparação, é necessário ler os dois arquivos na íntegra para calcular os CRCs - Se você comparar os dados em pequenos pedaços, poderá sair da comparação como assim que você encontrar um byte diferente.
21330 Jason Williams

3

Além disso, se você já conhece os caminhos, não faz sentido verificar o diretório.

Em vez disso, eu recomendaria algo como isto:

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 adicionará uma pasta ou nome de arquivo a um caminho e verifique se há exatamente uma barra invertida entre o caminho e o nome.

File.OpenTextirá abrir um arquivo e criar um de uma StreamReadersó vez.

Ao prefixar uma string com @, você pode evitar ter que escapar de todas as barras invertidas (por exemplo, @"a\b\c")


3

Eu acho que posso ter encontrado uma maneira sintaticamente mais limpa de declarar isso usando declaração, e parece funcionar para mim? usar var como seu tipo na instrução using em vez de IDisposable parece inferir dinamicamente o tipo em ambos os objetos e me permite instanciar meus objetos e chamar suas propriedades e métodos da classe com a qual eles estão alocados, como em

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

Se alguém sabe por que isso não está certo, por favor me avise


1
Vários em uma linha funcionam se todas as coisas forem do mesmo tipo. Os tipos mistos precisam se dividir em separado usando () s. Mas ele não funciona com var, você tem que especificar um tipo (C # 5 especificação, p237)
Chris F Carroll

0

É a maneira normal de usar e funciona perfeitamente. Embora existam outras maneiras de implementar isso. Quase todas as respostas já estão presentes na resposta desta pergunta. Mas aqui estou listando todos eles juntos.

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

Opção 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;
      }
    }
  }

opção 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;
       }
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.