Вложено с помощью операторов в C #


316

Я работаю над проектом. Я должен сравнить содержимое двух файлов и посмотреть, точно ли они соответствуют друг другу.

Перед многими проверками и проверкой ошибок мой первый черновик:

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

Кажется немного странным иметь вложенные usingутверждения.

Есть лучший способ сделать это?


Я думаю, что, возможно, нашел синтаксически более чистый способ объявления этого выражения с помощью, и это, кажется, работает для меня? использование var в качестве типа в выражении using вместо IDisposable, по-видимому, позволяет мне создавать экземпляры обоих моих объектов и вызывать их свойства и методы класса, которому они выделены, как при использовании (var uow = UnitOfWorkType1 (), uow2 = UnitOfWorkType2 ()) {}
Калеб

Ответы:


557

Предпочтительный способ сделать это - поставить открывающую скобку только {после последнего usingоператора, например так:

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

10
Очиститель? и также не заставляет вас использовать одни и те же типы .. Я всегда делаю это таким образом, даже если типы соответствуют читабельности и согласованности.
meandmycode

7
@Hardryv: автоформат Visual Studio удаляет его. Идея состоит в том, чтобы выглядеть как список переменных.
Суббота

41
Не уверен, что я нахожу это более читабельным. Во всяком случае, это нарушает внешний вид вложенного кода. И выглядит так, как будто первый оператор using пуст и не используется. Но, я думаю, что когда-либо работает ...: /
Джонатон Уотни

10
@ Брайан Уоттс, «противоположности» могут выражать реальные предпочтения. Очень вероятно, что другая группа разработчиков разошлась бы, если бы было рекомендовано размещение. Единственный способ узнать это - снова запустить эксперимент в параллельной вселенной.
Дэн Розенстарк

6
@fmuecke: это не очень верно; это сработает. Правила для того, IDisposableчтобы звонить Dispose()дважды, ничего не должны делать. Это правило только в случае плохо написанных одноразовых изделий.
SLaks

138

Если объекты одного типа, вы можете сделать следующее

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

1
Ну, они все одного и того же типа, если они все IDisposable, может быть, литье будет работать?
jpierson

8
@jpierson, который работает, да, но тогда, когда вы вызываете IDisposableобъекты из блока using, мы не можем вызвать ни одного из членов класса (без приведения, который побеждает точку imo).
Коннелл

IDisposable - это тип, поэтому просто используйте его в качестве типа, чтобы получить список смешанных типов, как видно из нескольких других ответов.
Крис Роллинз

33

Когда IDisposables одного типа, вы можете сделать следующее:

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

На странице MSDN usingесть документация по этой языковой функции.

Вы можете сделать следующее, независимо от того, IDisposableотносятся ли к одному типу:

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

18

если вы не возражаете объявить переменные для вашего блока using перед блоком using, вы можете объявить их все в одном выражении using.

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

Таким образом, x и y - это просто переменные-заполнители типа ID, доступные для использования блоком using, и вы используете t и u внутри своего кода. Просто подумал, что упомяну.


3
Я чувствую, что это может сбить с толку нового разработчика, который смотрит на ваш код.
Зак

5
Это может быть плохой практикой; у него есть побочный эффект: переменные будут существовать даже после освобождения неуправляемых ресурсов. Согласно справке Microsoft по C #: «Вы можете создать экземпляр объекта ресурса и затем передать переменную в оператор using, но это не лучший метод. В этом случае объект остается в области действия после того, как элемент управления покидает блок using, даже если он будет вероятно, больше не имеют доступа к своим неуправляемым ресурсам ".
Роберт Альтман

@RobertAltman Вы правы, и в реальном коде я бы использовал другой подход (вероятно, от Гэвина Х). Это просто менее предпочтительная альтернатива.
Botz3000

Вы можете просто переместить объявления внутри использования с типами. Это было бы лучше?
Тимоти Блейсделл

9

Если вы хотите сравнить файлы эффективно, вообще не используйте StreamReaders, и тогда в использовании нет необходимости - вы можете использовать низкоуровневое чтение потоков, чтобы получить буферы данных для сравнения.

Вы также можете сначала сравнить такие вещи, как размер файла, чтобы быстро обнаружить разные файлы, чтобы избавить себя от необходимости читать все данные тоже.


Да, проверка размера файла является хорошей идеей, экономит ваше время или читает все байты. (+1)
TimothyP

9

Оператор using работает с интерфейсом IDisposable, поэтому другим вариантом может быть создание составного класса некоторого типа, который реализует IDisposable и имеет ссылки на все объекты IDisposable, которые вы обычно помещаете в оператор using. Обратной стороной этого является то, что вы должны сначала объявить свои переменные и выходить за пределы области видимости, чтобы они были полезны в блоке using, требуя больше строк кода, чем требовалось бы для некоторых других предложений.

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

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

В этом случае конструктор для DisposableCollection является массивом params, поэтому вы можете вводить сколько угодно.


7

Вы также можете сказать:

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

Но некоторые люди могут найти это трудно читать. Кстати, как оптимизация вашей проблемы, почему вы не проверяете, что размеры файлов одинаковы, прежде чем переходить строка за строкой?


6

Вы можете опустить скобки для всех, кроме самых внутренних, используя:

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

Я думаю, что это чище, чем помещать несколько одинаковых типов в одно и то же использование, как предлагали другие, но я уверен, что многие люди будут думать, что это сбивает с толку


6

Вы можете сгруппировать несколько одноразовых объектов в один оператор использования с запятыми:

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

}

5

В этом нет ничего странного. usingэто сокращенный способ обеспечения удаления объекта после завершения блока кода. Если у вас есть одноразовый предмет во внешнем блоке, который должен использовать внутренний блок, это вполне приемлемо.

Изменить: Слишком медленный набор текста, чтобы показать пример консолидированного кода. +1 всем остальным.


5

И чтобы просто внести ясность, в этом случае, поскольку каждый последующий оператор является одним оператором (а не блоком), вы можете опустить все квадратные скобки:

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;  

Интересное решение; Выполнение этого / даже использование 1 набора скобок на самом низком уровне, возможно, выполняет ту же цель, что и выравнивание их по левому краю (более чистое ИМО), одновременно обращаясь к желанию косметического вложения, о котором другие упоминали, чтобы показать любое подчинение.
user1172173

5

Начиная с C # 8.0 вы можете использовать объявление об использовании .

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

Это избавит от использования переменных в конце области видимости переменных, то есть в конце метода.


3

Они появляются время от времени, когда я тоже пишу код. Вы могли бы рассмотреть перемещение второго оператора using в другую функцию?


3

Вы также спрашиваете, есть ли лучший способ сравнить с файлами? Я предпочитаю рассчитывать CRC или MD5 для обоих файлов и сравнивать их.

Например, вы можете использовать следующий метод расширения:

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

Как только вы это сделаете, сравните файлы довольно просто:

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

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

Вы можете использовать встроенный класс System.Security.Cryptography.MD5, но вычисляемый хеш является байтом [], поэтому вам все равно придется сравнивать эти два массива.


2
Вместо того, чтобы брать байтовый массив, метод должен взять Streamобъект и вызывать ReadByteметод, пока он не вернет -1. Это сэкономит большие объемы памяти для больших файлов.
Слоты

Как бы вы рассчитали CRC по всем байтам?
TimothyP

О, не берите в голову то, что я сказал: p Thnx, я изменю это в моем коде: p Мы используем это только для данных <1000 байтов, поэтому еще не заметили проблем, но все равно изменится
TimothyP

Каждый раз, когда вы вызываете ReadByteпродвижение позиции потока на один байт. Поэтому, если вы продолжите вызывать его, пока он не вернет -1 (EOF), он даст вам каждый байт в файле. msdn.microsoft.com/en-us/library/system.io.stream.readbyte.aspx
SLaks

7
Использование CRC прекрасно, если вы хотите сравнить несколько файлов несколько раз, но для одного сравнения вы должны прочитать оба файла целиком, чтобы вычислить CRC. Если вы сравниваете данные небольшими порциями, вы можете выйти из сравнения как как только вы найдете байт, который отличается.
Джейсон Уильямс

3

Кроме того, если вы уже знаете пути, нет смысла сканировать каталог.

Вместо этого я бы порекомендовал что-то вроде этого:

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 добавит папку или имя файла к пути и удостоверится, что между путем и именем есть только одна обратная косая черта.

File.OpenTextоткроет файл и создаст за StreamReaderодин раз.

Приставляя строку к @, вы можете избежать экранирования от обратной косой черты (например, @"a\b\c")


3

Я думаю, что, возможно, нашел синтаксически более чистый способ объявления этого выражения с помощью, и это, кажется, работает для меня? использование var в качестве типа в выражении using вместо IDisposable, по-видимому, динамически выводит тип для обоих объектов и позволяет мне создавать экземпляры обоих моих объектов и вызывать их свойства и методы класса, которому они выделены, как в

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

случае, если кто-нибудь знает, почему это не правильно, пожалуйста, дайте мне знать


1
Несколько на одной строке работает, если все вещи одного типа. Смешанные типы должны разделяться на отдельные с помощью (). Но это не работает с var, вы должны указать тип (спецификация C # 5, p237)
Крис Ф. Кэрролл,

0

Это нормальный способ использования и работает идеально. Хотя есть и другие способы реализации этого. Почти каждый ответ уже присутствует в ответе на этот вопрос. Но здесь я перечисляю их всех вместе.

Уже использован

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

Опция 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;
      }
    }
  }

Вариант 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;
       }
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.