如何使用 .net StreamReader 打开已打开的文件?

58

我有一些csv文件,作为测试工具的一部分使用。在Excel中打开了这些文件后,我就不能够像往常一样打开并读取它们了,否则我会遇到一个IOException错误:

System.IO.IOException:由于文件正在被另一个进程使用,因此无法访问文件“TestData.csv”。

下面是一段测试工具的代码片段:

using (CsvReader csv = new CsvReader(new StreamReader(new FileStream(fullFilePath, FileMode.Open, FileAccess.Read)), false))
{
    // Process the file
}

这是StreamReader的一个限制吗?我可以在其他应用程序中打开该文件(例如Notepad ++),因此不能是操作系统问题。也许我需要使用其他类?如果有人知道如何绕过这个问题(除了关闭Excel!),我会非常感激。

5个回答

169

正如Jared所说,除非拥有文件的其他实体允许共享读取,否则您无法这样做。 Excel允许共享读取,即使对于它正在写入的文件。因此,您必须使用FileShare.ReadWrite参数打开文件流。

FileShare参数经常被误解,它表示文件的其他打开者可以做什么。它适用于过去和未来的打开者。不要将FileShare视为对以前打开者(例如Excel)的反向禁止,而是一个不能在当前打开或任何将来的打开中违反的约束。

在当前尝试打开文件的情况下,FileShare.Read表示“只有在先前的打开者(例如Excel)仅为读取打开了该文件时,才能成功地为我打开此文件。”如果您在打开文件时指定FileShare.Read,而该文件正由Excel进行写入,则您的打开将失败,因为它会违反约束,因为Excel已经以写入方式打开了它。

因为Excel正在写入该文件,所以如果您想要您的打开成功,必须使用FileShare.ReadWrite打开该文件。FileShare参数的另一种思考方式:它指定“另一个人的文件访问”。

现在假设有一个不同的场景,在该场景中,您正在打开尚未被任何其他应用程序打开的文件。FileShare.Read表示“未来的打开者只能使用读取访问权限打开该文件”。

从逻辑上讲,这些语义是有意义的-FileShare.Read表示,如果其他人已经在写入它,您不希望读取该文件,如果您已经在读取它,您也不希望其他人写入该文件。 FileShare.ReadWrite表示,即使另一个人在写入它,您也愿意读取该文件,并且在您读取它时,您没有问题让另一个打开者写入该文件。

在任何情况下,这都不允许多个编写者。 FileShare类似于数据库隔离级别。这里所需的设置取决于您需要的“一致性”保证。

例如:

using (Stream s = new FileStream(fullFilePath, 
                                 FileMode.Open,
                                 FileAccess.Read,
                                 FileShare.ReadWrite))
{
  ...
}
或者,
using (Stream s = System.IO.File.Open(fullFilePath, 
                                      FileMode.Open, 
                                      FileAccess.Read, 
                                      FileShare.ReadWrite))
{
}

补充说明:

System.IO.FileShare 的文档有点简略。如果您想了解更准确的信息,请查看Win32 CreateFile 函数的文档,该文档更好地解释了 FileShare 的概念。


1
太棒了的解释!!!谢谢Cheeso,我的项目因为这个问题一直悬而未决...但现在不再是了... - w4ik
非常有用!基于此创建了一个函数,使用两个连续的try catch语句来安全地打开在Word或Excel中已经被使用的文档。第一次尝试使用FileShare.Read打开,第二次使用FileShare.ReadWrite打开。 - Koert van Kleef
获取到 FileStream 后,您可以使用 StreamReader 进行操作。请参见 https://dev59.com/BHVC5IYBdhLWcg3wdw65#286556。 - CAD bloke

16

编辑

我仍不完全确定为什么这是正确答案,但您可以通过在FileStream构造函数中传递FileShare.ReadWrite来解决此问题。

using (CsvReader csv = new CsvReader(new StreamReader(new FileStream(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)), false)
{
  ...
}

我目前很好奇,正在试图理解为什么这是特定的答案。如果我以后理解了,我会更新这个信息。

最佳文档实际上似乎在CreateFile函数中。这是.Net在底层调用以打开文件(创建文件有点不准确)的函数。它有更好的文档介绍如何共享打开文件的方面。另一个选项是只需阅读Cheeso的答案。


我尝试了你的建议,但似乎 Excel 不太友好(惊喜!)。不过如果其他程序能够打开该文件,那肯定有可能打开它吧? - Jon Cage
@Jon,不幸的是,如果其他进程没有使用FileShare.Read等效方式打开文件,则您无法在该进程关闭它之前打开该文件。其他程序能够做到这一点的方法是使用FileShare.Read打开。Office以独占访问方式打开并防止其他人读取文件,这在某种程度上是引人注目的。 - JaredPar
2
@Jared:也许我没有表达清楚最后一点。Notepad++可以打开文件,尽管它已经在Excel中打开,但我的C#测试平台失败了。所以这是可能的,但也许不是使用StreamReader? - Jon Cage
@Jon 很有趣。我认为StreamReader不是问题,因为听起来你在managed版本的CreateFile上遇到了错误。我会试着玩一下看看能否找出解决方法。 - JaredPar
1
@Jon,我更新了我的答案。使用FileShare.ReadWrite而不是FileShare.Read。您需要匹配其他进程的隐式共享设置。在这种情况下,它们正在共享读取和写入。 - JaredPar
显示剩余2条评论

5
如果另一个进程已经打开了文件,则通常可以使用File.Copy,然后打开该副本。虽然不是一种优雅的解决方案,但却是一种务实的解决方案。

不是最好的解决方案,但在没有其他选择的情况下,我可能会被诱惑采用这种方法 - 谢谢。 - Jon Cage

0
另一个需要注意的地方是,如果你使用 FileShare.ReadWrite 打开了一个 FileStream,那么后续打开该文件的操作也必须指定 FileShare.ReadWrite,否则会出现“另一个进程正在使用此文件”的错误。

-6

使用 System.Diagnostics;

您可以简单地调用 Process.Start("文件名&路径")

不确定是否有帮助,但这就是我刚刚在我们的内部网络上实现预览 PDF 按钮所使用的方法。


2
-1: 这会启动一个外部程序来读取文件,但无法帮助我打开它,以便我可以在自己的程序中读取内容。 - Jon Cage

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接