tags:

views:

412

answers:

5

I have to modify a text in a file programatically with C#. What's the best way to do this and avoid rewriting the entire file content. I want to edit only some words and save it.

+2  A: 

You can append to an existing file using StreamWriter and AppendText easily.

If you want to do more serious modifications, then I think you must read the contents of the file into memory in C# using StreamReader for example, modifying the contents programmatically of that stream, and then re-writing the file with the stream's contents.

I think that's how it is anyway.

Sev
A: 

It's probably simpler to read the file in and modify it then read it out again.

To really be efficient (i.e. not read/write the whole file for every operation) you need to do some house keeping to keep track of metadata about the file, and then do binary editing.

You'll still have to manage the case where the file grows and needs to be appended, etc.

John Weldon
+1  A: 

When you open a file for writing there are only two possible outcomes. Either you open it for appending which will allow you to write at the end only, or you open it for normal write in which case you will write from the beginning of the file. There is no "open at mid point, write then shift remaining content according to insertion length".

It is possible that some API will allow such modifications but behind the scenes, they will only allow you those two basic operations. You shouldn't worry to much about rewriting the whole file however, it is not such a bad operation unless your file is huge and your hard drive is very slow. You should worry more about the number of times you do it instead.

  1. Open the file
  2. Read the whole file into a variable or array
  3. Insert modifications
  4. Write back the file
Eric
+2  A: 

As others have said, you can easily append to an existing file. Inserting data in the middle of the file requires that you write out the remainder of the file. There's no way to just shift everything in the file. If you want to overwrite individual bytes in place, you can open a FileStream. You then use the "Seek" to go to the specific bytes you want to overwrite, and use Write, or WriteByte to overwrite the existing data with new data.

Kibbee
+1  A: 

If you are targeting windows, you can use the winapi CreateFile, ReadFile, WriteFile, etc. You can easily write to any spot in a file and write as much data as you want. You do not have to rewrite the file. This doesn't even require unsafe code. I limited the functionality of the functions (no asynch or wacky file mappings) just to get this done quickly, but it can read and write files no problem. To get this example to work, make a small text file 100 bytes or so, and make sure it is in the location of the exe. A little bit of this code comes from the MSDN website. I have modified it significantly though.

using System;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Windows.Forms;
using Path = System.IO.Path;

class Program {

  static void Main() {

    string sTestFile = Path.Combine(Path.GetDirectoryName(
     Application.ExecutablePath), "Test.txt");

    // test reading a file
    WinApiFile File = new WinApiFile(sTestFile,
     WinApiFile.DesiredAccess.GENERIC_READ);
    byte[] aBuffer = new byte[1000];
    uint cbRead = File.Read(aBuffer, 1000);
    File.Close();

    // Test writing the file
    File.Open(WinApiFile.DesiredAccess.GENERIC_WRITE);
    // write the time to a 10 byte offset in the file
    // conver the date time to byte array
    int i = 0;
    foreach (var ch in DateTime.Now.ToString()) 
      aBuffer[i++] = (byte)ch;
    // now write it out
    File.MoveFilePointer(10);  // 10 byte offset
    File.Write(aBuffer, (uint)i);  // write the time
    File.Dispose();
  }
}

public class WinApiFile : IDisposable {

  /* ---------------------------------------------------------
   * private members
   * ------------------------------------------------------ */
  private SafeFileHandle _hFile = null;
  private string _sFileName = "";
  private bool _fDisposed;

  /* ---------------------------------------------------------
   * properties
   * ------------------------------------------------------ */
  public bool IsOpen { get { return (_hFile != null); } }
  public SafeFileHandle Handle { get { return _hFile; } }
  public string FileName {
    get { return _sFileName; }
    set {
      _sFileName = (value ?? "").Trim();
      if (_sFileName.Length == 0)
        CloseHandle(_hFile);
    }
  }
  public int FileLength {
    get {
      return (_hFile != null) ? (int)GetFileSize(_hFile,
       IntPtr.Zero) : 0;
    }
    set {
      if (_hFile == null)
        return;
      MoveFilePointer(value, MoveMethod.FILE_BEGIN);
      if (!SetEndOfFile(_hFile))
        ThrowLastWin32Err();
    }
  }

  /* ---------------------------------------------------------
   * Constructors
   * ------------------------------------------------------ */

  public WinApiFile(string sFileName,
   DesiredAccess fDesiredAccess) {
    FileName = sFileName;
    Open(fDesiredAccess);
  }
  public WinApiFile(string sFileName,
   DesiredAccess fDesiredAccess,
   CreationDisposition fCreationDisposition) {
    FileName = sFileName;
    Open(fDesiredAccess, fCreationDisposition);
  }

  /* ---------------------------------------------------------
   * Open/Close
   * ------------------------------------------------------ */

  public void Open(
   DesiredAccess fDesiredAccess) {
    Open(fDesiredAccess, CreationDisposition.OPEN_EXISTING);
  }

  public void Open(
   DesiredAccess fDesiredAccess,
   CreationDisposition fCreationDisposition) {
    ShareMode fShareMode;
    if (fDesiredAccess == DesiredAccess.GENERIC_READ) {
      fShareMode = ShareMode.FILE_SHARE_READ;
    } else {
      fShareMode = ShareMode.FILE_SHARE_NONE;
    }
    Open(fDesiredAccess, fShareMode, fCreationDisposition, 0);
  }

  public void Open(
   DesiredAccess fDesiredAccess, 
   ShareMode fShareMode, 
   CreationDisposition fCreationDisposition, 
   FlagsAndAttributes fFlagsAndAttributes) {

    if (_sFileName.Length == 0)
      throw new ArgumentNullException("FileName");
    _hFile = CreateFile(_sFileName, fDesiredAccess, fShareMode, 
     IntPtr.Zero, fCreationDisposition, fFlagsAndAttributes, 
     IntPtr.Zero);
    if (_hFile.IsInvalid) {
      _hFile = null;
      ThrowLastWin32Err();
    }
    _fDisposed = false;

  }

  public void Close() {
    if (_hFile == null)
      return;
    _hFile.Close();
    _hFile = null;
    _fDisposed = true;
  }

  /* ---------------------------------------------------------
   * Move file pointer 
   * ------------------------------------------------------ */

  public void MoveFilePointer(int cbToMove) {
    MoveFilePointer(cbToMove, MoveMethod.FILE_CURRENT);
  }

  public void MoveFilePointer(int cbToMove, 
   MoveMethod fMoveMethod) {
    if (_hFile != null)
      if (SetFilePointer(_hFile, cbToMove, IntPtr.Zero, 
       fMoveMethod) == INVALID_SET_FILE_POINTER)
        ThrowLastWin32Err();
  }

  public int FilePointer {
    get {
      return (_hFile != null) ? (int)SetFilePointer(_hFile, 0, 
       IntPtr.Zero, MoveMethod.FILE_CURRENT) : 0;
    }
    set {
      MoveFilePointer(value);
    }
  }

  /* ---------------------------------------------------------
   * Read and Write
   * ------------------------------------------------------ */

  public uint Read(byte[] buffer, uint cbToRead) {
    // returns bytes read
    uint cbThatWereRead = 0;
    if (!ReadFile(_hFile, buffer, cbToRead, 
     ref cbThatWereRead, IntPtr.Zero))
      ThrowLastWin32Err();
    return cbThatWereRead;
  }

  public uint Write(byte[] buffer, uint cbToWrite) {
    // returns bytes read
    uint cbThatWereWritten = 0;
    if (!WriteFile(_hFile, buffer, cbToWrite, 
     ref cbThatWereWritten, IntPtr.Zero))
      ThrowLastWin32Err();
    return cbThatWereWritten;
  }

  /* ---------------------------------------------------------
   * IDisposable Interface
   * ------------------------------------------------------ */
  public void Dispose() {
    Dispose(true);
    GC.SuppressFinalize(this);
  }

  protected virtual void Dispose(bool fDisposing) {
    if (!_fDisposed) {
      if (fDisposing) {
        if (_hFile != null)
          _hFile.Dispose();
        _fDisposed = true;
      }
    }
  }

  ~WinApiFile() {
    Dispose(false);
  }

  /* ---------------------------------------------------------
   * WINAPI STUFF
   * ------------------------------------------------------ */

  private void ThrowLastWin32Err() {
    Marshal.ThrowExceptionForHR(
     Marshal.GetHRForLastWin32Error());
  }

  [Flags]
  public enum DesiredAccess : uint {
    GENERIC_READ = 0x80000000,
    GENERIC_WRITE = 0x40000000
  }
  [Flags]
  public enum ShareMode : uint {
    FILE_SHARE_NONE = 0x0,
    FILE_SHARE_READ = 0x1,
    FILE_SHARE_WRITE = 0x2,
    FILE_SHARE_DELETE = 0x4,

  }
  public enum MoveMethod : uint {
    FILE_BEGIN = 0,
    FILE_CURRENT = 1,
    FILE_END = 2
  }
  public enum CreationDisposition : uint {
    CREATE_NEW = 1,
    CREATE_ALWAYS = 2,
    OPEN_EXISTING = 3,
    OPEN_ALWAYS = 4,
    TRUNCATE_EXSTING = 5
  }
  [Flags]
  public enum FlagsAndAttributes : uint {
    FILE_ATTRIBUTES_ARCHIVE = 0x20,
    FILE_ATTRIBUTE_HIDDEN = 0x2,
    FILE_ATTRIBUTE_NORMAL = 0x80,
    FILE_ATTRIBUTE_OFFLINE = 0x1000,
    FILE_ATTRIBUTE_READONLY = 0x1,
    FILE_ATTRIBUTE_SYSTEM = 0x4,
    FILE_ATTRIBUTE_TEMPORARY = 0x100,
    FILE_FLAG_WRITE_THROUGH = 0x80000000,
    FILE_FLAG_OVERLAPPED = 0x40000000,
    FILE_FLAG_NO_BUFFERING = 0x20000000,
    FILE_FLAG_RANDOM_ACCESS = 0x10000000,
    FILE_FLAG_SEQUENTIAL_SCAN = 0x8000000,
    FILE_FLAG_DELETE_ON = 0x4000000,
    FILE_FLAG_POSIX_SEMANTICS = 0x1000000,
    FILE_FLAG_OPEN_REPARSE_POINT = 0x200000,
    FILE_FLAG_OPEN_NO_CALL = 0x100000
  }

  public const uint INVALID_HANDLE_VALUE = 0xFFFFFFFF;
  public const uint INVALID_SET_FILE_POINTER = 0xFFFFFFFF;
  // Use interop to call the CreateFile function.
  // For more information about CreateFile,
  // see the unmanaged MSDN reference library.
  [DllImport("kernel32.dll", SetLastError = true)]
  internal static extern SafeFileHandle CreateFile(
   string lpFileName,
   DesiredAccess dwDesiredAccess, 
   ShareMode dwShareMode, 
   IntPtr lpSecurityAttributes,
   CreationDisposition dwCreationDisposition, 
   FlagsAndAttributes dwFlagsAndAttributes,
   IntPtr hTemplateFile);

  [DllImport("kernel32", SetLastError = true)]
  internal static extern Int32 CloseHandle(
   SafeFileHandle hObject);

  [DllImport("kernel32", SetLastError = true)]
  internal static extern bool ReadFile(
   SafeFileHandle hFile, 
   Byte[] aBuffer,
   UInt32 cbToRead,
   ref UInt32 cbThatWereRead, 
   IntPtr pOverlapped);

  [DllImport("kernel32.dll", SetLastError = true)]
  internal static extern bool WriteFile(
   SafeFileHandle hFile, 
   Byte[] aBuffer,
   UInt32 cbToWrite, 
   ref UInt32 cbThatWereWritten, 
   IntPtr pOverlapped);

  [DllImport("kernel32.dll", SetLastError = true)]
  internal static extern UInt32 SetFilePointer(
   SafeFileHandle hFile,
   Int32 cbDistanceToMove, 
   IntPtr pDistanceToMoveHigh,
   MoveMethod fMoveMethod);

  [DllImport("kernel32.dll", SetLastError = true)]
  internal static extern bool SetEndOfFile(
   SafeFileHandle hFile);

  [DllImport("kernel32.dll", SetLastError = true)]
  internal static extern UInt32 GetFileSize(
   SafeFileHandle hFile, 
   IntPtr pFileSizeHigh);

}
johnnycrash
Why on earth would you want to do that instead of using the classes that come with the framework?
Niki
I was lead to believe that there is not a class that lets you write a couple bytes to the middle of a file without reading the entire file in and then writing it all out.
johnnycrash
Ha! but there is a way using FileStream seek. I haven't been using C# long enough! Ok so my whole post is a waste!
johnnycrash