18

How do I read all lines of a text file that is also open in Excel into string[] without getting IO exception?

There is this question which could be a part of the answer, though I don't know how I could use what's in there: How do I open an already opened file with a .net StreamReader?

Community
  • 1
  • 1
user1306322
  • 7,713
  • 15
  • 53
  • 113
  • What don't understand about the answer you linked to? It is pretty clear. Have you tried it? Shows us your code. – Polyfun Oct 05 '12 at 10:57
  • What do I do with that code to populate my `string[] lines` with lines of a text file? There's nothing about it there. – user1306322 Oct 05 '12 at 11:06
  • http://stackoverflow.com/questions/3560651/whats-the-least-invasive-way-to-read-a-locked-file-in-c-sharp-perhaps-in-unsaf – m0s Apr 30 '14 at 10:55
  • @m0s still doesn't answer how to read all lines into a string array – user1306322 Apr 30 '14 at 11:05
  • @user1306322 You can use the ReadLine method of a StreamReader to get a single line and add to a list of strings. When done convert the list to an array. The direct answer to your question is you can't use File.ReadAllLines on a locked file. If you still can't figure out how to use a streamreader to read a file into array ask it as a separate question. Lastly you can try to make a temporary copy of your locked file and then try File.ReadAllLines on the copy. – m0s Apr 30 '14 at 21:44
  • @m0s can you post that as an answer with a proper working code example? – user1306322 May 01 '14 at 03:57
  • @user1306322 the reason isn't actually in `ReadAllLines`. The source of the exception comes from the underlying Stream that is created. The Stream cannot open a file that is locked for writing by another application. You have to provide your own Stream that can open that file. Check out my answer to see how and why. – psubsee2003 Jun 21 '14 at 13:33

3 Answers3

52

Your problem is Excel opens the file as read/write. File.ReadAllLines() cannot access the file when it is open for writing in another application. If you opened the csv in Excel as read only, you wouldn't encounter this exception.

This is because the implementation in .Net does not open the internal stream with appropriate permissions to access the file when another application has write permissions to it.

So the fix here is simple, write your own ReadAllLines() method that sets the appropriate permissions when initiating the underlying Stream.

Here's an idea that borrows heavily from what ReadAllLines() does on its own:

public string[] WriteSafeReadAllLines(String path)
{
    using (var csv = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    using (var sr = new StreamReader(csv))
    {
        List<string> file = new List<string>();
        while (!sr.EndOfStream)
        {
            file.Add(sr.ReadLine());
        }

        return file.ToArray();
    }
}

The only difference between this and what ReadAllLines does is the FileShare permission is set to FileShare.ReadWrite, which allows the file to be opened even when it is open with Read/Write permissions in another application.

Now, you have to understand the issues that can arise from this as there can be complications since another application has write permissions to the file.

  1. You are going to be reading the last saved version of the file, so if you have unsaved changes in Excel, this method will not read them
  2. If you save the file in Excel while this method is in the middle of reading it you are going to probably get an exception depending on the circumstances. This is because the file is completely locked while it is saving, so if you try to read the file while it locked, it will throw an System.IO.IOException.
  3. And if you save the file and manage to avoid an exception (extremely unlikely, but possible given specific timing), you are going to read the newly saved file, not the original.

To understand why you cannot read the file when it is open for writing by another application, you have to look at the actual implementation in .NET. (This is the implementation in .Net 4.5 so it may be slightly different if you are looking at a difference version of .Net).

This is what File.ReadAllLines() actually looks like:

public static string[] ReadAllLines(string path)
{
  if (path == null)
    throw new ArgumentNullException("path");
  if (path.Length == 0)
    throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath"));
  else
    return File.InternalReadAllLines(path, Encoding.UTF8);
}


private static string[] InternalReadAllLines(string path, Encoding encoding)
{
  List<string> list = new List<string>();
  using (StreamReader streamReader = new StreamReader(path, encoding))
  {
    string str;
    while ((str = streamReader.ReadLine()) != null)
      list.Add(str);
  }
  return list.ToArray();
}

And to peek at what StreamReader is doing internally:

internal StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool checkHost)
{
  if (path == null || encoding == null)
    throw new ArgumentNullException(path == null ? "path" : "encoding");
  if (path.Length == 0)
    throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath"));
  if (bufferSize <= 0)
    throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
  this.Init((Stream) new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan, Path.GetFileName(path), false, false, checkHost), encoding, detectEncodingFromByteOrderMarks, bufferSize, false);
}

So here we come to the reason why the exception is throw, when supplied with a path, StreamReader creates a FileStream that has the FileShare parameter set to Read. This means that it cannot share a file with another application with Read/Write access to the file. To override this behavior you need to give it a Stream with a different setting for FileShare, which is what I did in the solution I provided above.

psubsee2003
  • 8,115
  • 8
  • 58
  • 76
  • 1
    This makes a lot of sense and I'll trust that it works for now. – user1306322 Jun 21 '14 at 14:01
  • @user1306322 it worked for me. I just ran into the same exact problem with reading a CSV I had open in excel and found your question, but wasn't satisified with the answers, so this is how I sloved it. So if you have any complications, (aside from the ones I mentioned), let me know. – psubsee2003 Jun 21 '14 at 14:05
  • Probably the best answer on Stack Overflow @psubsee2003 – Tom McDonough Oct 25 '19 at 13:25
  • Excellent! I had issue reading lines from a log file that was kept open. This fixed it.Upvoted. – NoBullMan Nov 04 '20 at 21:03
2

You cannot open file for reading only when it is opened with a read restriction. Otherwise all methods including ReadAllLines will work without throwing permission exceptions.

AgentFire
  • 8,224
  • 6
  • 39
  • 84
1

Try this.

FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read,
System.IO.FileShare.ReadWrite)