Вкладено за допомогою операторів у C #


315

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

Перед багато перевірки та перевірки помилок, моя перша чернетка:

  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 як вашого типу в операторі use замість IDisposable, здається, дозволяє мені інстанціювати обидва мої об'єкти та називати їх властивості та методи класу, з яким вони виділяються, як при використанні (var uow = UnitOfWorkType1 (), uow2 = UnitOfWorkType2 ()) {}
Калеб

Відповіді:


556

Кращий спосіб зробити це - поставити вступну дужку лише {після останнього usingтвердження, наприклад цього:

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

10
Прибиральник? а також не змушує вас використовувати одні й ті ж типи .. Я завжди роблю це таким чином, навіть якщо типи відповідають за читабельність та послідовність.
meandmycode

7
@Hardryv: Автоформат Visual Studio видаляє його. Ідея полягає в тому, щоб виглядати як список змінних декларацій.
SLaks

41
Не впевнений, чи мені взагалі здається, що це читабельніше. Якщо що-небудь, це порушує вигляд вкладеного коду. І виглядає так, ніби перший оператор, що використовується, порожній та невикористаний. Але, я здогадуюсь, що коли-небудь працює ...: /
Jonathon Watney

10
@Bryan Watts, "противники" можуть виявляти реальні переваги. Дуже ймовірно, що інша група дияволів не погодилася б вкладати вкладиші. Єдиний спосіб дізнатися - знову запустити експеримент у паралельній всесвіті.
Дан Розенстарк

6
@fmuecke: Це не дуже правда; це спрацює. Правила, які IDisposableзаявляють, що дзвонити Dispose()двічі, нічого не повинні робити. Це правило є лише у випадку погано написаного одноразового використання.
СЛАкс

138

Якщо об’єкти одного типу, ви можете зробити наступне

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

1
Ну, вони все однотипні, якщо вони всі IDisposable, можливо, акторський склад буде працювати?
jpierson

8
@jpierson, що працює, так, але тоді, коли ви викликаєте IDisposableоб'єкти зсередини використовуючого блоку, ми не можемо викликати жодного з членів класу (без виступу, який перемагає точку 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

якщо ви не заперечуєте оголошувати змінні для вашого блоку перед використанням блоку, ви можете оголосити їх усі в одному і тому ж використанні оператора.

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

Таким чином, x і y - це просто змінні заповнювачі типу ID, які використовуються для використання блоку, який використовується, і ви використовуєте t і u всередині свого коду. Просто думав, що згадаю.


3
Я відчуваю, що це буде заплутати нового розробника, який дивиться на ваш код.
Зак

5
Це може бути поганою практикою; це має побічний ефект, що змінні будуть існувати навіть після звільнення некерованих ресурсів. Згідно з посиланням на C # Майкрософт, "Ви можете створити об'єкт ресурсу, а потім передати змінну до використовуваного оператора, але це не найкраща практика. У цьому випадку об'єкт залишається в області застосування після того, як контроль залишає блок використання, хоча він буде певно, більше немає доступу до своїх некерованих ресурсів ".
Роберт Альтман

@RobertAltman Ви маєте рацію, і в реальному коді я б застосував інший підхід (можливо, той, що від Гевіна Н). Це лише менш бажана альтернатива.
Botz3000

Ви можете просто перемістити декларації всередині програми за допомогою typecasts. Це було б краще?
Тімоті Блейсделл

9

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

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


Так, перевірка розміру файлу - це гарна ідея, економить ваш час або читає всі байти. (+1)
ТимофійП

9

Використовуючи оператор вимикає інтерфейс IDisposable, тому іншим варіантом може бути створення певного типу складеного класу, який реалізує IDisposable і містить посилання на всі об'єкти, що використовуються IDisposable, які ви зазвичай містили б у своєму операторі using. Суть до цього полягає в тому, що ви повинні оголосити свої змінні спочатку і поза рамками, щоб вони були корисними в блоці, що вимагає більше рядків коду, ніж потребують деякі інші пропозиції.

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

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

Конструктор для DisposableCollection - це масив парам в цьому випадку, щоб ви могли подавати стільки, скільки вам потрібно.


7

Ви також можете сказати:

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

Але деяким людям може бути важко прочитати. BTW, як оптимізація вашої проблеми, чому ви не перевіряєте, чи розміри файлів спочатку однакового розміру, перш ніж переходити рядок за рядком?


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 набір дужок на найнижчому рівні, можливо, досягає тієї ж мети, що і їх складання ліворуч (чистіший ІМО), вирішуючи при цьому бажання косметичного введення інших осіб, які зазначили будь-яке підпорядкування.
користувач1172173

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

Вони з'являються час від часу, коли я також кодую. Ви можете розглянути можливість переміщення другого використовуючого оператора в іншу функцію?


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 по всіх байтах?
ТимофійП

О, неважливо, що я сказав: 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 як вашого типу в операторі use замість IDisposable, здається, динамічно підводить тип обох об'єктів і дозволяє мені інстанціювати обидва мої об'єкти та називати їх властивості та методи класу, з якого вони виділені, як у

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

випадку, якщо хтось знає, чому це не правильно, будь ласка, дайте мені знати


1
Кілька в одному рядку працюють, якщо всі речі одного типу. Змішані типи мають розділятися на окремі, використовуючи () s. Але це не працює з var, вам потрібно вказати тип (специфікація C # 5, p237)
Chris F Carroll

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.