Có cách nào để đóng StreamWriter mà không đóng BaseStream của nó không?


117

Vấn đề gốc rễ của tôi là khi usingcác cuộc gọi Disposetrên a StreamWriter, nó cũng xử lý BaseStream(cùng một vấn đề với Close).

Tôi có một giải pháp cho việc này, nhưng như bạn thấy, nó liên quan đến việc sao chép luồng. Có cách nào để thực hiện việc này mà không cần sao chép luồng không?

Mục đích của việc này là đưa nội dung của một chuỗi (ban đầu được đọc từ cơ sở dữ liệu) vào một luồng, do đó, luồng có thể được đọc bởi một thành phần bên thứ ba.
NB : Tôi không thể thay đổi thành phần của bên thứ ba.

public System.IO.Stream CreateStream(string value)
{
    var baseStream = new System.IO.MemoryStream();
    var baseCopy = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        baseStream.WriteTo(baseCopy); 
    }
    baseCopy.Seek(0, System.IO.SeekOrigin.Begin);
    return baseCopy;
}

Được dùng như

public void Noddy()
{
    System.IO.Stream myStream = CreateStream("The contents of this string are unimportant");
    My3rdPartyComponent.ReadFromStream(myStream);
}

Lý tưởng nhất là tôi đang tìm kiếm một phương pháp tưởng tượng được gọi là BreakAssociationWithBaseStream, ví dụ:

public System.IO.Stream CreateStream_Alternate(string value)
{
    var baseStream = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        writer.BreakAssociationWithBaseStream();
    }
    return baseStream;
}

Đây là một câu hỏi tương tự: stackoverflow.com/questions/2620851
Jens Granlund,

Tôi đang thực hiện việc này với một luồng từ WebRequest, thú vị là bạn có thể đóng nó nếu mã hóa là ASCII nhưng không phải UTF8. Kỳ dị.
tofutim

tofutim, tôi đã có tôi mã hóa như ASCII, và nó vẫn disposes của dòng cơ bản ..
Gerard ONeill

Câu trả lời:


121

Nếu bạn đang sử dụng .NET Framework 4.5 trở lên, có quá tải StreamWriter khi sử dụng mà bạn có thể yêu cầu mở luồng cơ sở khi trình ghi đóng .

Trong các phiên bản cũ hơn của .NET Framework trước 4.5, StreamWriter giả sử rằng nó sở hữu luồng. Các tùy chọn:

  • Đừng vứt bỏ StreamWriter; chỉ cần xả nó.
  • Tạo một trình bao bọc luồng bỏ qua các lệnh gọi đến Close/ Disposenhưng proxy mọi thứ khác. Tôi có một triển khai điều đó trong MiscUtil , nếu bạn muốn lấy nó từ đó.

15
Rõ ràng là quá tải 4,5 không phải là một nhượng bộ không được nghĩ ra - quá tải yêu cầu kích thước bộ đệm, không thể là 0 hoặc null. Trong nội bộ, tôi biết rằng 128 ký tự là kích thước tối thiểu, vì vậy tôi chỉ đặt nó thành 1. Nếu không thì 'tính năng' này khiến tôi hài lòng.
Gerard ONeill

Có cách nào để đặt leaveOpentham số đó sau khi StreamWriterđược tạo không?
c00000fd

@ c00000fd: Tôi không biết.
Jon Skeet

1
@Yepeekai: "nếu tôi chuyển một luồng cho một phương thức con và phương thức con đó tạo StreamWriter, nó sẽ bị xử lý khi kết thúc quá trình thực thi phương thức con đó" Không, điều đó đơn giản là không đúng. Nó sẽ chỉ được xử lý nếu có thứ gì đó kêu gọi Disposenó. Kết thúc phương thức không tự động làm điều đó. Nó có thể được hoàn thiện sau nếu nó có bản hoàn thiện, nhưng đó không phải là điều tương tự - và vẫn chưa rõ bạn đang lường trước nguy hiểm gì. Nếu bạn cho rằng trả về StreamWritertừ một phương thức là không an toàn vì nó có thể bị GC tự động xử lý, điều đó không đúng.
Jon Skeet

1
@Yepeekai: Và IIRC, StreamWriterkhông có công cụ hoàn thiện - tôi sẽ không mong đợi nó, chính xác là vì lý do này.
Jon Skeet

44

.NET 4.5 có phương pháp mới cho điều đó!

http://msdn.microsoft.com/EN-US/library/gg712853(v=VS.110,d=hv.2).aspx

public StreamWriter(
    Stream stream,
    Encoding encoding,
    int bufferSize,
    bool leaveOpen
)

Cảm ơn bạn đời! Tôi không biết điều này, và nếu có bất cứ điều gì đó sẽ là lý do chính đáng để tôi bắt đầu nhắm mục tiêu .NET 4.5!
Vectovox

22
Xấu hổ là không có tình trạng quá tải không yêu cầu đặt bufferSize. Tôi hài lòng với mặc định ở đó. Tôi phải tự mình vượt qua nó. Không phải la tận cung của thê giơi.
Andy McCluggage

3
Mặc định bufferSize1024. Thông tin chi tiết có tại đây .
Alex Klaus,

35

Đơn giản là không gọi Disposetrên StreamWriter. Lý do lớp này dùng một lần không phải vì nó chứa tài nguyên không được quản lý mà là để cho phép xử lý luồng mà bản thân nó có thể chứa tài nguyên không được quản lý. Nếu tuổi thọ của dòng bên dưới được xử lý ở nơi khác, không cần phải loại bỏ người viết.


2
@Marc, sẽ không gọi Flushthực hiện công việc trong trường hợp nó đệm dữ liệu?
Darin Dimitrov

3
Tốt thôi, nhưng khi chúng ta thoát khỏi CreateStream, StreamWrtier có thể thu thập được, buộc người đọc phần thứ ba phải chạy đua với GC, đây không phải là tình huống tôi muốn ở lại. Hay tôi đang thiếu thứ gì đó?
Binary Worrier

9
@BinaryWorrier: Không, không có điều kiện chủng tộc: StreamWriter không có trình hoàn thiện (và thực sự là không nên).
Jon Skeet

10
@Binary Worrier: Bạn chỉ nên có bản hoàn thiện nếu bạn trực tiếp sở hữu tài nguyên. Trong trường hợp này, StreamWriter sẽ giả định rằng Luồng sẽ tự dọn dẹp nếu cần.
Jon Skeet

2
Có vẻ như phương thức 'đóng' của StreamWriter cũng đóng và loại bỏ luồng. Vì vậy, người ta phải xả, nhưng không đóng hoặc loại bỏ trình phát trực tiếp, vì vậy nó không đóng luồng, điều này sẽ làm tương đương với việc loại bỏ luồng. Quá nhiều "sự trợ giúp" từ API ở đây.
Gerard ONeill

5

Luồng bộ nhớ có thuộc tính ToArray có thể được sử dụng ngay cả khi luồng bị đóng. Mảng ghi nội dung luồng vào một mảng byte, bất kể thuộc tính Vị trí. Bạn có thể tạo luồng mới dựa trên luồng bạn đã viết.

public System.IO.Stream CreateStream(string value)
{
    var baseStream = new System.IO.MemoryStream();
    var baseCopy = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        baseStream.WriteTo(baseCopy); 
    }
    var returnStream = new System.IO.MemoryStream( baseCopy.ToArray());
    return returnStream;
}

Điều đó có giới hạn chính xác mảng được trả về với kích thước nội dung không? Bởi vì Stream.Positioncó thể không được gọi sau khi nó được xử lý.
Nyerguds

2

Bạn cần tạo một hậu duệ của StreamWriter và ghi đè phương thức hủy bỏ của nó, bằng cách luôn chuyển sai cho tham số giải quyết, nó sẽ buộc trình ghi luồng KHÔNG đóng, StreamWriter chỉ gọi vứt bỏ trong phương thức đóng, vì vậy không cần phải ghi đè nó (tất nhiên bạn có thể thêm tất cả các hàm tạo nếu bạn muốn, tôi chỉ có một):

public class NoCloseStreamWriter : StreamWriter
{
    public NoCloseStreamWriter(Stream stream, Encoding encoding)
        : base(stream, encoding)
    {
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(false);
    }
}

3
Tôi tin rằng điều này không làm những gì bạn nghĩ. Các disposinglá cờ là một phần của các IDisposablemô hình . Luôn chuyển falsetới Dispose(bool)phương thức của lớp cơ sở về cơ bản báo hiệu StreamWriterrằng nó đang được gọi từ trình hoàn thiện (điều này không đúng khi bạn gọi Dispose()một cách rõ ràng) và do đó không nên truy cập vào bất kỳ đối tượng được quản lý nào. Đây là lý do tại sao nó sẽ không loại bỏ luồng cơ sở. Tuy nhiên, cách bạn đạt được điều này là một vụ hack; sẽ đơn giản hơn nhiều nếu không gọi Disposengay từ đầu!
stakx - không còn góp

Đó là thực sự của Symantec, bất cứ điều gì bạn làm ngoài việc viết lại luồng hoàn toàn từ đầu sẽ trở thành một vụ hack. Mặc dù vậy, chắc chắn là bạn không thể gọi base.Dispose (false), nhưng sẽ không có sự khác biệt về chức năng và tôi thích sự rõ ràng của ví dụ của mình. Tuy nhiên, hãy nhớ điều này, một phiên bản trong tương lai của lớp StreamWriter có thể làm được nhiều việc hơn là chỉ đóng luồng khi nó xử lý, vì vậy việc gọi hủy bỏ (sai) cũng chứng minh điều đó. Nhưng để mỗi riêng của mình.
Aaron Murgatroyd

2
Một cách khác để làm điều đó sẽ là tạo trình bao bọc luồng của riêng bạn chứa một luồng khác trong đó phương thức Đóng chỉ đơn giản là không làm gì thay vì đóng luồng bên dưới, đây không phải là một vụ hack mà là một công việc nhiều hơn.
Aaron Murgatroyd

Thời điểm tuyệt vời: Tôi vừa định đề xuất điều tương tự (lớp trang trí, có thể được đặt tên OwnedStream, bỏ qua Dispose(bool)Close).
stakx - không còn đóng góp vào

Vâng, đoạn mã ở trên là cách tôi thực hiện cho một phương pháp nhanh chóng và dễ hiểu, nhưng nếu tôi đang tạo một ứng dụng thương mại hoặc một cái gì đó thực sự quan trọng đối với tôi, tôi sẽ thực hiện chính xác bằng cách sử dụng lớp Stream wrapper. Cá nhân tôi nghĩ rằng Microsoft đã thực hiện một sai lầm ở đây, StreamWriter nên đã có một tài sản boolean để đóng dòng cơ bản thay vào đó, nhưng sau đó tôi không làm việc tại Microsoft để họ làm những gì họ như tôi đoán: D
Aaron Murgatroyd
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.