EJS
EJS

Reputation: 1071

C# Overwriting file with StreamWriter created from FileStream

I need to manipulate the contents of a file:

 FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);               
 StreamReader sr = new StreamReader(fs);
 StreamWriter sw = new StreamWriter(fs);
 newString = someStringTransformation(sr.ReadToEnd());
 sw.Write(newString);
 fs.flush();
 fs.Close();

However the above appends the newString instead of overwriting the file with the new changes. It needs to be done so that no other application can access the file in between reading a writing which is why I'm creating the reader and writer from a FileStream object.

I know that you can create a StreanWriter with the second parameter set to false as described here. However when creating the StreamWriter as above that does not seem to be one of the parameters.

Upvotes: 24

Views: 35151

Answers (6)

Just use:

FileStream fs = System.IO.File.Create(filePath);

File.Create will create or overwrite a file and return the filestream for it.

Upvotes: 5

Vitaliy Prushak
Vitaliy Prushak

Reputation: 1162

Maybe it will help one.

Just use FileMode.Open or FileMode.Truncate to overwrite the file:

namespace System.IO
{
    //
    // Summary:
    //     Specifies how the operating system should open a file.
    [ComVisible(true)]
    public enum FileMode
    {
        ...
        //
        // Summary:
        //     Specifies that the operating system should create a new file. If the file already
        //     exists, it will be overwritten. This requires System.Security.Permissions.FileIOPermissionAccess.Write
        //     permission. FileMode.Create is equivalent to requesting that if the file does
        //     not exist, use System.IO.FileMode.CreateNew; otherwise, use System.IO.FileMode.Truncate.
        //     If the file already exists but is a hidden file, an System.UnauthorizedAccessException
        //     exception is thrown.
        Create = 2,
        //
        ...
    }

or

namespace System.IO
{
    //
    // Summary:
    //     Specifies how the operating system should open a file.
    [ComVisible(true)]
    public enum FileMode
    {
        ...
        //
        // Summary:
        //     Specifies that the operating system should open an existing file. When the file
        //     is opened, it should be truncated so that its size is zero bytes. This requires
        //     System.Security.Permissions.FileIOPermissionAccess.Write permission. Attempts
        //     to read from a file opened with FileMode.Truncate cause an System.ArgumentException
        //     exception.
        Truncate = 5,
        ...
    }

Upvotes: 3

Gusdor
Gusdor

Reputation: 14334

The problem you are having is that reading from the stream advances to the end of the file. Further writes will then append.

This will achieve a full overwrite.

using(FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
{
    StreamReader sr = new StreamReader(fs);
    using (StreamWriter sw = new StreamWriter(fs))
    {
        newString = someStringTransformation(sr.ReadToEnd());

        // discard the contents of the file by setting the length to 0
        fs.SetLength(0); 

        // write the new content
        sw.Write(newString);
    }
}

Why use SetLength? Your new content might be shorter than the existing string! The last thing you want is the old content at the end of your file.

Upvotes: 38

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186668

You can avoid these low-level Stream's and their Reader/Writers by using Linq:

  File.WriteAllText(filePath, someStringTransformation(File.ReadAllText(filePath)));

Upvotes: 2

Thomas
Thomas

Reputation: 2984

What you could do is repositioning the streams and also removing buffered data to be sure that nothing gets in the way. Taking your example:

FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);               
 StreamReader sr = new StreamReader(fs);
 StreamWriter sw = new StreamWriter(fs);
 newString = someStringTransformation(sr.ReadToEnd());

    sr.Position = 0;
    sr.DiscardBufferedData(); 

    sw.Position = 0;

 sw.Write(newString);
 fs.flush();
 fs.Close();

if the new data is less than the old data you would need to truncate the remaining data. By using sw.SetLength(newString.Length);.

Upvotes: 1

Lasse V. Karlsen
Lasse V. Karlsen

Reputation: 391276

There are several steps you need to take here but let me make my assumptions clear:

You need to keep the file open and locked during the entire operation to prevent others from accessing the file during this time.

With that said, here's what you need to do:

  1. You need to read the contents using the StreamReader, as you've done
  2. You need to reposition the underlying stream back to the start, its position have been changed by reading through the reader
  3. You need to write out the transformed contents through the StreamWriter, as you've done
  4. You need to flush the writer because of the next step
  5. You need to truncate the underlying stream/file to its current position, to handle a transformation that shortens the contents.

The code for all of this could look like this LINQPad program:

void Main()
{
    const string filePath = @"d:\temp\test.txt";
    var encoding = Encoding.UTF8;
    using (var stream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
    using (var reader = new StreamReader(stream, encoding))
    using (var writer = new StreamWriter(stream, encoding))
    {
        // Read
        var contents = reader.ReadToEnd();

        // Transform
        var transformedContents = contents.Substring(0, Math.Max(0, contents.Length - 1));

        // Write out transformed contents from the start of the file
        stream.Position = 0;
        writer.Write(transformedContents);
        writer.Flush();

        // Truncate
        stream.SetLength(stream.Position);
    }
}

Upvotes: 4

Related Questions