Bersarang menggunakan pernyataan dalam C #


316

Saya sedang mengerjakan sebuah proyek. Saya harus membandingkan konten dua file dan melihat apakah mereka cocok satu sama lain secara tepat.

Sebelum banyak pengecekan dan validasi kesalahan, konsep pertama saya adalah:

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

Tampaknya agak aneh untuk memiliki usingpernyataan bersarang .

Apakah ada cara yang lebih baik untuk melakukan ini?


Saya pikir saya mungkin telah menemukan cara yang secara sintaksis lebih bersih untuk mendeklarasikan pernyataan menggunakan ini, dan sepertinya itu bekerja untuk saya? menggunakan var sebagai tipe Anda dalam pernyataan menggunakan bukan IDisposable tampaknya memungkinkan saya untuk instantiate kedua objek saya dan memanggil properti dan metode kelas yang dialokasikan dengan mereka, seperti dalam menggunakan (var uow = UnitOfWorkType1 (), uow2 = UnitOfWorkType2 ()) {}
Caleb

Jawaban:


557

Cara yang lebih disukai untuk melakukan ini adalah dengan hanya memasang brace pembuka {setelah usingpernyataan terakhir , seperti ini:

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

10
Pembersih? dan juga tidak memaksa Anda untuk menggunakan jenis yang sama .. Saya selalu melakukannya dengan cara ini bahkan jika jenisnya cocok untuk keterbacaan dan konsistensi.
meandmycode

7
@Hardryv: Format otomatis Visual Studio menghapusnya. Idenya adalah untuk terlihat seperti daftar deklarasi variabel.
SLaks

41
Tidak yakin apakah itu lebih mudah dibaca sama sekali. Jika ada yang merusak tampilan kode bersarang. Dan sepertinya pernyataan penggunaan pertama kosong dan tidak digunakan. Tapi, saya kira apa yang pernah berhasil ...: /
Jonathon Watney

10
@Bryan Watts, "contrarians" mungkin mengekspresikan preferensi nyata. Sangat mungkin bahwa kelompok dev yang berbeda akan berbeda pendapat jika bersarang direkomendasikan. Satu-satunya cara untuk mengetahui adalah menjalankan percobaan lagi di alam semesta paralel.
Dan Rosenstark

6
@ fmuecke: Itu tidak terlalu benar; ini akan bekerja. Aturan untuk IDisposablemenyatakan bahwa menelpon Dispose()dua kali seharusnya tidak melakukan apa-apa. Aturan itu hanya dalam kasus pakai sekali pakai yang ditulis dengan buruk.
SLaks

138

Jika objek dari jenis yang sama Anda dapat melakukan hal berikut

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

1
Yah mereka semua adalah tipe yang sama jika mereka semua IDisposable mungkin para pemain akan bekerja?
jpierson

8
@jpierson yang berfungsi, ya, tapi kemudian ketika Anda memanggil IDisposableobjek dari dalam blok menggunakan, kami tidak dapat memanggil anggota kelas mana pun (tanpa pemain, yang mengalahkan titik imo).
Connell

IDisposable adalah tipe jadi gunakan saja sebagai tipe untuk memiliki daftar tipe campuran, seperti yang terlihat dalam beberapa jawaban lainnya.
Chris Rollins

33

Ketika IDisposables dari jenis yang sama, Anda dapat melakukan hal berikut:

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

Halaman MSDN pada usingmemiliki dokumentasi tentang fitur bahasa ini.

Anda dapat melakukan yang berikut apakah IDisposablememiliki tipe yang sama atau tidak :

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

18

jika Anda tidak keberatan mendeklarasikan variabel untuk blok yang Anda gunakan sebelum blok yang digunakan, Anda bisa mendeklarasikan semuanya dalam pernyataan menggunakan yang sama.

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

Dengan begitu, x dan y hanyalah variabel tempat penampung dari tipe ID yang dapat digunakan untuk menggunakan blok dan Anda menggunakan t dan u di dalam kode Anda. Saya pikir saya akan menyebutkan.


3
Saya merasa ini akan membingungkan bagi pengembang baru yang melihat kode Anda.
Zack

5
Ini bisa menjadi praktik yang buruk; itu memiliki efek samping bahwa variabel akan tetap ada bahkan setelah sumber daya yang tidak dikelola telah dibebaskan. Menurut referensi C # Microsoft, "Anda bisa instantiate objek sumber daya dan kemudian meneruskan variabel ke pernyataan menggunakan, tetapi ini bukan praktik terbaik. Dalam hal ini, objek tetap dalam lingkup setelah kontrol meninggalkan blok menggunakan meskipun itu akan mungkin tidak lagi memiliki akses ke sumber daya yang tidak dikelola. "
Robert Altman

@ Roberttmanman Anda benar, dan dalam kode nyata saya akan menggunakan pendekatan lain (mungkin yang dari Gavin H). Ini hanya alternatif yang kurang disukai.
Botz3000

Anda bisa memindahkan deklarasi di dalam use with typecasts. Apakah itu lebih baik?
Timothy Blaisdell

9

Jika Anda ingin membandingkan file secara efisien, jangan gunakan StreamReaders sama sekali, dan penggunaannya tidak perlu - Anda dapat menggunakan pembacaan stream level rendah untuk menarik buffer data untuk dibandingkan.

Anda juga dapat membandingkan hal-hal seperti ukuran file terlebih dahulu untuk dengan cepat mendeteksi file yang berbeda untuk menyelamatkan diri Anda karena harus membaca semua data juga.


Ya, memeriksa ukuran file adalah ide yang bagus, menghemat waktu Anda atau membaca semua byte. (+1)
TimothyP

9

Pernyataan menggunakan bekerja dari antarmuka IDisposable sehingga opsi lain bisa membuat beberapa jenis kelas komposit yang mengimplementasikan IDisposable dan memiliki referensi ke semua objek IDisposable yang biasanya Anda taruh dalam pernyataan menggunakan Anda. Sisi buruknya adalah Anda harus mendeklarasikan variabel Anda terlebih dahulu dan di luar ruang lingkup agar mereka berguna di dalam blok menggunakan yang membutuhkan lebih banyak baris kode daripada yang dibutuhkan oleh beberapa saran lainnya.

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

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

Konstruktor untuk DisposableCollection adalah array params dalam kasus ini sehingga Anda dapat memberi makan sebanyak yang Anda suka.


7

Anda juga bisa mengatakan:

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

Tetapi beberapa orang mungkin kesulitan membaca. BTW, sebagai pengoptimalan untuk masalah Anda, mengapa Anda tidak memeriksa bahwa ukuran file adalah ukuran yang sama terlebih dahulu, sebelum pergi baris demi baris?


6

Anda bisa menghilangkan tanda kurung pada semua kecuali bagian paling dalam menggunakan:

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

Saya pikir ini lebih bersih daripada menempatkan beberapa jenis yang sama dalam penggunaan yang sama, seperti yang disarankan orang lain, tapi saya yakin banyak orang akan berpikir ini membingungkan


6

Anda dapat mengelompokkan beberapa objek sekali pakai dalam satu pernyataan-menggunakan dengan koma:

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

}

5

Tidak ada yang aneh dengan itu. usingadalah cara singkat untuk memastikan pembuangan objek setelah blok kode selesai. Jika Anda memiliki objek sekali pakai di blok luar yang perlu digunakan blok dalam, ini bisa diterima.

Sunting: Terlalu lambat pada pengetikan untuk menampilkan contoh kode terkonsolidasi. Memberi +1 kepada orang lain.


5

Dan untuk menambah kejelasan, dalam hal ini, karena setiap pernyataan berturut-turut adalah pernyataan tunggal, (dan bukan blok), Anda dapat menghilangkan semua tanda kurung:

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;  

Solusi menarik; melakukan ini / bahkan menggunakan 1 set tanda kurung pada tingkat terendah mungkin, mencapai tujuan yang sama dengan menumpuknya dibenarkan (IMO bersih), sambil mengatasi keinginan bersarang kosmetik yang disebutkan orang lain untuk menunjukkan subordinasi.
user1172173

5

Karena C # 8.0 Anda dapat menggunakan deklarasi menggunakan .

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

Ini akan membuang variabel menggunakan di akhir ruang lingkup variabel, yaitu di akhir metode.


3

Ini muncul dari waktu ke waktu ketika saya kode juga. Anda bisa mempertimbangkan memindahkan pernyataan menggunakan kedua ke fungsi lain?


3

Apakah Anda juga bertanya apakah ada cara yang lebih baik untuk dibandingkan dengan file? Saya lebih suka menghitung CRC atau MD5 untuk kedua file dan membandingkannya.

Misalnya, Anda dapat menggunakan metode ekstensi berikut:

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

Setelah selesai, cukup mudah untuk membandingkan file:

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

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

Anda bisa menggunakan kelas System.Security.Cryptography.MD5 bawaan, tetapi hash yang dihitung adalah byte [] sehingga Anda masih harus membandingkan dua array tersebut.


2
Alih-alih mengambil array byte, metode harus mengambil Streamobjek dan memanggil ReadBytemetode hingga mengembalikan -1. Ini akan menghemat banyak memori untuk file besar.
SLaks

Bagaimana Anda kemudian menghitung crc atas semua byte?
TimothyP

Oh, tidak apa-apa yang saya katakan: p Thnx, saya akan mengubahnya dalam kode saya: p Kami hanya menggunakannya untuk data <1000 byte jadi belum melihat masalah tetapi akan tetap berubah
TimothyP

Setiap kali Anda menelepon ReadByte, posisi stream bertambah satu byte. Oleh karena itu, jika Anda terus memanggilnya sampai mengembalikan -1 (EOF), itu akan memberi Anda setiap byte dalam file. msdn.microsoft.com/en-us/library/system.io.stream.readbyte.aspx
SLaks

7
Menggunakan CRC sangat bagus jika Anda ingin membandingkan banyak file beberapa kali, tetapi untuk perbandingan tunggal Anda harus membaca kedua file secara keseluruhan untuk menghitung CRC - Jika Anda membandingkan data dalam potongan kecil maka Anda dapat keluar dari perbandingan sebagai Segera setelah Anda menemukan byte yang berbeda.
Jason Williams

3

Juga, jika Anda sudah tahu jalurnya, tidak ada gunanya memindai direktori.

Sebagai gantinya, saya akan merekomendasikan sesuatu seperti ini:

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 akan menambahkan folder atau nama file ke jalur dan memastikan bahwa ada persis satu garis miring terbalik antara jalur dan nama.

File.OpenText akan membuka file dan membuat file StreamReader dalam sekali jalan.

Dengan awalan string dengan @, Anda dapat menghindari keharusan melarikan diri dari setiap garis miring terbalik (misalnya, @"a\b\c")


3

Saya pikir saya mungkin telah menemukan cara yang secara sintaksis lebih bersih untuk mendeklarasikan pernyataan menggunakan ini, dan sepertinya itu bekerja untuk saya? menggunakan var sebagai tipe Anda dalam pernyataan menggunakan alih-alih IDisposable tampaknya secara dinamis menyimpulkan tipe pada kedua objek dan memungkinkan saya untuk instantiate kedua objek saya dan memanggil properti dan metode mereka dari kelas yang dialokasikan dengan mereka, seperti dalam

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

Jika ada yang tahu mengapa ini tidak benar, tolong beri tahu saya


1
Beberapa baris bekerja jika semua jenisnya sama. Jenis campuran harus dipisah terpisah menggunakan () s. Tapi itu tidak bekerja dengan var, Anda harus menentukan tipe (spesifikasi C # 5, p237)
Chris F Carroll

0

Ini cara penggunaan normal dan bekerja sempurna. Meskipun ada beberapa cara lain untuk mengimplementasikan ini. Hampir setiap jawaban sudah ada dalam jawaban pertanyaan ini. Tapi di sini saya daftar semuanya.

Sudah Digunakan

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

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

pilihan 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;
       }
    }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.