Lồng nhau sử dụng các câu lệnh trong C #


315

Tôi đang làm việc trên một dự án. Tôi phải so sánh nội dung của hai tập tin và xem chúng có khớp chính xác với nhau không.

Trước rất nhiều kiểm tra lỗi và xác nhận, dự thảo đầu tiên của tôi là:

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

Có vẻ hơi kỳ quặc khi có những usingtuyên bố lồng nhau .

Có cách nào tốt hơn để làm điều này?


Tôi nghĩ rằng tôi có thể đã tìm thấy một cách khai báo cú pháp rõ ràng hơn bằng cách sử dụng câu lệnh này và nó có vẻ hiệu quả với tôi không? sử dụng var làm kiểu của bạn trong câu lệnh sử dụng thay vì IDis Dùng dường như cho phép tôi khởi tạo cả hai đối tượng của mình và gọi các thuộc tính và phương thức của lớp mà chúng được phân bổ, như khi sử dụng (var uow = UnitOfWorkType1 (), uow2 = UnitOfWorkType2 ()) {}
Caleb

Câu trả lời:


556

Cách ưa thích để làm điều này là chỉ đặt một dấu ngoặc mở {sau usingcâu lệnh cuối cùng , như thế này:

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

10
Sạch hơn? và cũng không bắt buộc bạn phải sử dụng các loại tương tự .. Tôi luôn làm theo cách này ngay cả khi các loại phù hợp với tính dễ đọc và nhất quán.
meandmycode

7
@Hardryv: Định dạng tự động của Visual Studio sẽ xóa nó. Ý tưởng là trông giống như một danh sách các khai báo biến.
SLaks

41
Không chắc chắn nếu tôi thấy rằng dễ đọc hơn cả. Nếu bất cứ điều gì nó phá vỡ giao diện của mã lồng nhau. Và có vẻ như câu lệnh sử dụng đầu tiên là trống rỗng và không được sử dụng. Nhưng, tôi đoán những gì từng hoạt động ...: /
Jonathon Watney

10
@Bryan Watts, "người phản đối" có thể bày tỏ sở thích thực sự. Rất có khả năng một nhóm các nhà phát triển khác nhau sẽ không đồng ý lồng nhau. Cách duy nhất để biết là chạy lại thí nghiệm trong một vũ trụ song song.
Dan Rosenstark

6
@fmuecke: Điều đó không đúng lắm; nó sẽ làm việc Các quy tắc cho IDisposablenhà nước rằng gọi Dispose()hai lần không nên làm gì. Quy tắc đó chỉ trong trường hợp văn bản kém.
SLaks

138

Nếu các đối tượng cùng loại, bạn có thể làm như sau

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

1
Chà tất cả họ đều cùng loại nếu tất cả họ đều có thể sử dụng IDis, có lẽ một diễn viên sẽ làm việc?
jpierson

8
@jpierson không hoạt động, vâng, nhưng sau đó khi bạn gọi các IDisposableđối tượng từ bên trong khối sử dụng, chúng ta không thể gọi bất kỳ thành viên nào trong lớp (không có diễn viên, đánh bại điểm imo).
Connell

IDis Dùng là một loại vì vậy chỉ cần sử dụng nó làm loại để có danh sách các loại hỗn hợp, như được thấy trong một vài câu trả lời khác.
Chris Rollins

33

Khi các IDisposables cùng loại, bạn có thể làm như sau:

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

Trang MSDN trên usingcó tài liệu về tính năng ngôn ngữ này.

Bạn có thể làm như sau cho dù IDisposables có cùng loại hay không:

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

18

nếu bạn không khai báo các biến cho khối sử dụng của mình trước khối sử dụng, bạn có thể khai báo tất cả chúng trong cùng một câu lệnh sử dụng.

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

Theo cách đó, x và y chỉ là các biến giữ chỗ của loại IDis Dùng cho khối sử dụng để sử dụng và bạn sử dụng t và u bên trong mã của mình. Chỉ cần nghĩ rằng tôi sẽ đề cập đến.


3
Tôi cảm thấy như điều này sẽ gây nhầm lẫn cho một nhà phát triển mới nhìn vào mã của bạn.
Zack

5
Đây có thể là một thực hành xấu; nó có một tác dụng phụ là các biến vẫn sẽ tồn tại ngay cả sau khi nguồn được cung cấp không được quản lý đã được giải phóng. Theo tài liệu tham khảo C # của Microsoft, "Bạn có thể khởi tạo đối tượng tài nguyên và sau đó chuyển biến sang câu lệnh sử dụng, nhưng đây không phải là cách thực hành tốt nhất. Trong trường hợp này, đối tượng vẫn ở trong phạm vi sau khi điều khiển rời khỏi khối sử dụng mặc dù nó sẽ có lẽ không còn có quyền truy cập vào các tài nguyên không được quản lý của nó. "
Robert Altman

@RobertAltman Bạn nói đúng, và theo mã thực, tôi sẽ sử dụng một cách tiếp cận khác (có thể là cách tiếp cận từ Gavin H). Đây chỉ là một thay thế ít thích hợp hơn.
Botz3000

Bạn chỉ có thể di chuyển các khai báo bên trong bằng cách sử dụng các kiểu chữ. Điều đó sẽ tốt hơn?
Timothy Blaisdell

9

Nếu bạn muốn so sánh các tệp một cách hiệu quả, đừng sử dụng StreamReaders, và sau đó việc sử dụng là không cần thiết - bạn có thể sử dụng các luồng đọc mức thấp để lấy bộ đệm dữ liệu để so sánh.

Trước tiên, bạn cũng có thể so sánh những thứ như kích thước tệp để phát hiện nhanh các tệp khác nhau để tiết kiệm cho mình khi phải đọc tất cả dữ liệu.


Vâng, kiểm tra kích thước tệp là một ý tưởng tốt, giúp bạn tiết kiệm thời gian hoặc đọc tất cả các byte. (+1)
TimothyP

9

Câu lệnh sử dụng hoạt động trên giao diện IDis Dùng để có thể tùy chọn khác là tạo một số loại lớp tổng hợp thực hiện IDis Dùng một lần và có tham chiếu đến tất cả các đối tượng IDis Dùng mà bạn thường đưa vào câu lệnh sử dụng của mình. Mặt trái của vấn đề này là bạn phải khai báo các biến của mình trước và ngoài phạm vi để chúng có ích trong khối sử dụng yêu cầu nhiều dòng mã hơn một số đề xuất khác sẽ yêu cầu.

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

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

Hàm tạo cho Dùng một lần là một mảng params trong trường hợp này để bạn có thể cung cấp bao nhiêu tùy thích.


7

Bạn cũng có thể nói:

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

Nhưng một số người có thể thấy khó đọc. BTW, như một sự tối ưu hóa cho vấn đề của bạn, tại sao bạn không kiểm tra xem kích thước tệp có cùng kích thước trước khi đi từng dòng không?


6

Bạn có thể bỏ qua các dấu ngoặc trên tất cả trừ phần lớn sử dụng bên trong:

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

Tôi nghĩ rằng điều này sạch hơn so với việc sử dụng một số loại cùng loại trong cùng một cách sử dụng, như những người khác đã đề xuất, nhưng tôi chắc chắn nhiều người sẽ nghĩ rằng điều này là khó hiểu


6

Bạn có thể nhóm nhiều đối tượng dùng một lần trong một câu lệnh bằng dấu phẩy:

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

}

5

Không có gì kỳ lạ về nó. usinglà một cách tốc ký để đảm bảo xử lý đối tượng sau khi khối mã kết thúc. Nếu bạn có một đối tượng dùng một lần trong khối bên ngoài mà khối bên trong cần sử dụng, điều này là hoàn toàn chấp nhận được.

Chỉnh sửa: Quá chậm khi gõ để hiển thị ví dụ mã hợp nhất. +1 cho mọi người khác.


5

Và để thêm vào sự rõ ràng, trong trường hợp này, vì mỗi câu lệnh liên tiếp là một câu lệnh duy nhất, (và không phải là một khối), bạn có thể bỏ qua tất cả các dấu ngoặc:

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;  

Giải pháp thú vị; thực hiện điều này / thậm chí sử dụng 1 bộ dấu ngoặc ở mức thấp nhất có thể, hoàn thành mục tiêu tương tự như sắp xếp chúng hợp lý (IMO sạch hơn), trong khi giải quyết mong muốn làm tổ mỹ phẩm mà người khác đề cập để thể hiện bất kỳ sự phụ thuộc nào.
dùng1172173

5

C # 8.0, bạn có thể sử dụng khai báo .

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

Điều này sẽ loại bỏ các biến sử dụng ở cuối phạm vi của các biến, tức là ở cuối phương thức.


3

Chúng xuất hiện theo thời gian khi tôi viết mã là tốt. Bạn có thể xem xét chuyển câu lệnh thứ hai sang hàm khác?


3

Bạn cũng đang hỏi liệu có cách nào tốt hơn để so sánh với các tập tin không? Tôi thích tính CRC hoặc MD5 cho cả hai tệp và so sánh các tệp đó.

Ví dụ: bạn có thể sử dụng phương thức mở rộng sau:

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

Khi bạn đã hoàn thành việc so sánh các tệp khá dễ dàng:

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

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

Bạn có thể sử dụng lớp System.Security.Cryptography.MD5 tích hợp, nhưng hàm băm được tính toán là một byte [] vì vậy bạn vẫn phải so sánh hai mảng đó.


2
Thay vì lấy một mảng byte, phương thức sẽ lấy một Streamđối tượng và gọi ReadBytephương thức cho đến khi nó trả về -1. Điều này sẽ tiết kiệm lượng lớn bộ nhớ cho các tệp lớn.
SLaks

Làm thế nào bạn sẽ tính toán crc trên tất cả các byte?
TimothyP

Ôi đừng bận tâm những gì tôi đã nói: p Thnx, tôi sẽ thay đổi điều đó trong mã của mình: p Chúng tôi chỉ sử dụng nó cho dữ liệu <1000 byte nên chưa nhận thấy vấn đề nào nhưng sẽ thay đổi dù sao
TimothyP

Mỗi lần bạn gọi ReadBytevị trí của luồng sẽ tăng thêm một byte. Do đó, nếu bạn tiếp tục gọi nó cho đến khi nó trả về -1 (EOF), nó sẽ cung cấp cho bạn từng byte trong tệp. msdn.microsoft.com/en-us/l
Library / system.io.stream.readbyte.aspx

7
Sử dụng CRC là điều tuyệt vời nếu bạn muốn so sánh nhiều tệp nhiều lần, nhưng để so sánh một lần, bạn phải đọc toàn bộ cả hai tệp để tính CRC - Nếu bạn so sánh dữ liệu theo từng đoạn nhỏ thì bạn có thể thoát khỏi so sánh như ngay khi bạn tìm thấy một byte khác nhau.
Jason Williams

3

Ngoài ra, nếu bạn đã biết các đường dẫn, sẽ không có điểm nào đang quét thư mục.

Thay vào đó, tôi muốn giới thiệu một cái gì đó như thế này:

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 sẽ thêm một thư mục hoặc tên tệp vào một đường dẫn và đảm bảo rằng có chính xác một dấu gạch chéo ngược giữa đường dẫn và tên.

File.OpenTextsẽ mở một tập tin và tạo một StreamReadertrong một.

Bằng cách thêm tiền tố vào một chuỗi với @, bạn có thể tránh phải thoát mọi dấu gạch chéo ngược (ví dụ @"a\b\c":)


3

Tôi nghĩ rằng tôi có thể đã tìm thấy một cách khai báo cú pháp rõ ràng hơn bằng cách sử dụng câu lệnh này và nó có vẻ hiệu quả với tôi không? sử dụng var làm kiểu của bạn trong câu lệnh sử dụng thay vì IDis Dùng dường như tự động suy ra kiểu trên cả hai đối tượng và cho phép tôi khởi tạo cả hai đối tượng của mình và gọi các thuộc tính và phương thức của lớp mà chúng được phân bổ, như trong

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

Nếu có ai biết tại sao không đúng, xin vui lòng cho tôi biết


1
Một số trên một dòng hoạt động nếu tất cả mọi thứ cùng loại. Các loại hỗn hợp phải phân tách bằng cách sử dụng () s. Nhưng nó không hoạt động với var, bạn phải chỉ định một loại (đặc tả C # 5, p237)
Chris F Carroll

0

Đó là cách sử dụng bình thường và hoạt động hoàn hảo. Mặc dù có một số cách khác để thực hiện điều này. Hầu như mọi câu trả lời đã có mặt trong câu trả lời của câu hỏi này. Nhưng ở đây tôi liệt kê tất cả chúng cùng nhau.

Đã sử dụng

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

lựa chọ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;
      }
    }
  }

Lựa chọn 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;
       }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.