使用语句 FileStream 和/或 StreamReader - Visual Studio 2012 警告

Posted

技术标签:

【中文标题】使用语句 FileStream 和/或 StreamReader - Visual Studio 2012 警告【英文标题】:using statement FileStream and / or StreamReader - Visual Studio 2012 Warnings 【发布时间】:2012-08-17 05:15:13 【问题描述】:

新的 Visual Studio 2012 抱怨我一直使用的常见代码组合。我知道这看起来有点矫枉过正,但我​​在我的代码中做了以下“只是为了确定”。

using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))

    using (var sr = new StreamReader(fs))
    
        // Code here
    

Visual Studio 正在“警告”我,我不止一次处理 fs。所以我的问题是,写这个的正确方法是:

using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))

    var sr = new StreamReader(fs);
    // do stuff here

或者我应该这样做(或其他未提及的变体)。

var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

using (var sr = new StreamReader(fs))

    // Code here

我在 *** 中搜索了几个问题,但没有找到直接解决此组合最佳实践的问题。

谢谢!

【问题讨论】:

+1:好问题,JUbbard80! :) 在 VS2013 中这样的双重使用,FileStreamStreamReader,不会发出警告。是因为FileStreamStreamReader 的组合吗?或者 VS2013(相对于 VS2012)是否认识到这种特殊的双重处置是不成问题的? 【参考方案1】:

以下是Microsoft recommends的做法。它又长又笨重,但很安全:

FileStream fs = null;
try

    fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    using (TextReader tr= new StreamReader(fs))
    
        fs = null;
        // Code here
    

finally

    if (fs != null)
        fs.Dispose();

此方法将始终确保处理应处理的所有内容,尽管可能会引发什么异常。例如,如果StreamReader 构造函数抛出异常,FileStream 仍然会被正确处理。

【讨论】:

哦,这很方便。我没有意识到 CA2202(Microsoft Recommends 链接)中使用的示例与我的问题中的代码示例类似。 好。您可能也不会添加我刚刚编辑掉的额外逗号语法错误;)。 我添加了更多的解释,尽管我认为从示例和“为什么”的链接中可以清楚地看到。如果您认为某个答案提供了错误信息,则可以否决它。否则,只需投票赞成您的首选答案。如果这个答案几乎是微软所推荐的,那么这个答案几乎不会是错误信息。 这种模式很荒谬。多次处理对象通常是安全的(我不知道有一种情况不会)。这段代码质量很差,不应该被微软推荐。此外,在“fs = null;”之前发生异步异常的情况下,它是安全的。 @Pedro77 不需要,存在多个答案。只是试图警告人们,因为许多人会被接受的地位和高票数所诱惑(如果你没有其他方法来决定,作为初学者依赖这并不是一件愚蠢的事情)。不要使用此代码。【参考方案2】:

Visual Studio 正在“警告”我,我不止一次处理 fs。

你是,但没关系。 IDisposable.Dispose 的文档内容如下:

如果一个对象的 Dispose 方法被多次调用,则该对象必须忽略第一次之后的所有调用。如果对象的 Dispose 方法被多次调用,则该对象不得抛出异常。

基于此,警告是虚假的,我的选择是保留代码原样并禁止警告。

【讨论】:

【参考方案3】:

由于 Dan 的答案似乎只适用于 StreamWriter,我相信这可能是最可接受的答案。 (Dan 的回答仍然会使用 StreamReader 发出两次已处理的警告 - 正如 Daniel Hilgarth 和 exaceratedexpert 所提到的,StreamReader 会处理文件流)

using (TextReader tr = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))

    string line;
    while ((line = tr.ReadLine()) != null)
    
        // Do work here
    

这与 Daniel Hilgarth 的回答非常相似,修改为通过 StreamReader 上的 Using 语句调用 dispose,因为现在很清楚 StreamReader 将在 FileStream 上调用 dispose(根据所有其他帖子,参考文档)

更新:

我找到了这篇文章。为了它的价值。 Does disposing streamreader close the stream?

【讨论】:

但是不确定异常处理。我认为 FileStream 构造函数中会发生任何异常,因此不会使其进入 StreamReader 的初始化。因此,在这种情况下,我不必担心由于 StreamReader 中的异常而导致未处理的 FileStream。我当然可能是错的...... using (f(x)) 语句实际上编译为y = f(x); using(y) 。因此,using 语句的初始化程序抛出的任何异常都不会触发隐式 finally 语句。解决此问题的最佳方法是将构造函数设计为异常安全并在抛出后清理资源,我相信 Stream 类可以这样做。 就像我说的,我认为我的例子有点过头了,在StreamReader 类的情况下,我认为上面完全没问题,但如果你使用的是不同的类StreamReader,或者他们将来会更改实现,这样构造函数可能会在内部IDisposable 依赖项被实例化后抛出异常,然后内部IDisposable 将不会被释放。我仍然认为我发布的示例是最安全的。 Dan - 您的示例仍然会引发视觉工作室警告,这就是我将其作为答案删除的原因。它不会使用 StreamWriter 引发警告。只是 StreamReader 作为 streamreader 在内部处理对象,VS2012 知道这一点。 @JHubbard80:您现在使用的代码应该发出另一个警告,例如CA2000。【参考方案4】:

是的,正确的方法是使用您的第一个替代方案:

using (FileStream fs = new FileStream(filePath, FileMode.Open,
                                      FileAccess.Read, FileShare.ReadWrite)) 
 
    TextReader tr = new StreamReader(fs); 
    // do stuff here 
 

原因如下: 处理StreamReader 只会处理FileStream,所以这实际上是您唯一需要处理的事情。

您的第二个选项(只是内部“使用”)不是解决方案,因为如果 StreamReader 的构造函数内部出现异常,它将使 FileStream 未处理。

【讨论】:

我认为处置 StreamReader 对象很重要,以防该对象也有需要处置的项目。可以清楚地知道(通过反射,其他)StreamReader 只处理 FileStream 但作为日常类似的情况实践,我无法预测它。当向阅读器传递一个字符串参数时,我猜它会创建并处理它自己在内部创建的文件流。当传递一个文件流时,我假设它不会处理它作为 arg 接收的对象,因为它不知道该流是否会继续在外部使用。 @JHubbard80:你的假设是错误的。 StreamReader 确实处理了它传递的 Stream,这就是首先导致您发出警告的原因。不过你是对的,如果 (1) 类改变了它的实现并且 (2) 你正在将你的代码库切换到这个新版本,那么使用我的代码可能会有问题。如果您认为这是一个问题,请使用 Dan 提供的替代方案。我永远不会在像这样的简单场景中使用如此庞大的代码,因为它不会增加任何好处。 很公平。然而,它拥有处置在外部创建和传递的对象的所有权,这似乎很奇怪。我知道在这种特定情况下我不太可能在外部使用它,但我认为如果我在外部创建它,我应该负责选择何时处理它。再次感谢你的帮助。我想说你的两个答案都是正确的。接受答案将是抛硬币。 @JHubbard80:同意,我也会以不同的方式实现 StreamReader。这种奇怪的行为在过去确实困扰着我,因为在访问使用它的 StreamReader 后我还没有处理的流时,我得到了 ObjectDisposedExceptions... @exacerbatedexpert:啊哈。需要详细说明吗?【参考方案5】:

这是因为您使用StreamReader 的方式在释放流时将其释放。所以,如果你也处理流,它会被处理两次。有些人认为这是StreamReader 中的一个缺陷——但它仍然存在。在 VS 2012 (.NET 4.5) 中,StreamReader 中有一个选项可以使用新的构造函数不处理流:http://msdn.microsoft.com/en-us/library/gg712952

【讨论】:

我还建议使用扩展构造函数来避免这种巨大的麻烦。在将流分配给读取器的点和它知道应该处理传入的流的点之间,有很多事情可能会出错,这可能会阻止流被处理。在这种情况下,微软犯了一个错误,允许StreamReader 超越其责任。它可能源于始终处置IDisposable 资源的心态,但这种情况下的默认值应该是相反的,扩展选项应该是根据要求处置。 MemoryStream 对象更加明显。即使在阅读后,您也很可能不希望将其丢弃。主要用于单元测试,但它确实证明了一点。【参考方案6】:

两种解决方案:

A)您信任 Reflector 或 Documentation,并且您知道 *Reader*Writer 将关闭底层 *Stream。但是警告:如果抛出异常,它将不起作用。所以这不是推荐的方式:

using (TextReader tr = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))

    // Code here

B) 您忽略了警告,因为文档状态 The object must not throw an exception if its Dispose method is called multiple times. 这是推荐的方式,因为始终使用 using 是一种好习惯,并且在抛出异常的情况下是安全的:

[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
internal void myMethod()

    [...]
    using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    using (TextReader tr = new StreamReader(fs))
    
        // Code here
    

【讨论】:

【参考方案7】:

考虑到这个(完全合法的!)问题产生的所有废话,这将是我的偏好:

FileStream fs = null;
TextReader tr= null;
try

    fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    tr= new StreamReader(fs);
    // Code here

finally

    if (tr != null)
        tr.Dispose();
    if (fs != null)
        fs.Dispose();

下面的链接说明了完全合法的语法。 IMO,这种“使用”语法比嵌套的“使用”要好得多。但我承认 - 它确实没有解决了最初的问题:

http://blogs.msdn.com/b/ericgu/archive/2004/08/05/209267.aspx

.NET - Replacing nested using statements with single using statement

恕我直言...

【讨论】:

@palsm4 - (编辑 - 我刚刚意识到你现在承认这一点,但要向未来的读者强调)这个问题始于 Visual Studio 2012 警告。上面的代码仍然会引发这个警告。作为参考,我将上面的代码复制/粘贴到一个测试程序中,以验证仍然出现警告:“CA2202 不要多次处理对象对象'fs'可以被多次处理[...]。为了避免生成系统。 ObjectDisposedException 你不应该在一个对象上多次调用 Dispose。”这是因为 VS2012 知道 streamreader 正在处理文件流 所以我猜你的解决方案是“try/finally: IN; using: OUT” ;) 对吗? @paulsm4: -1: 你的堆叠使用仍然无法解决警告,侮辱他人不会给你信誉 1) “堆叠使用”(2 行 + 1 组大括号)只是一个选项。 IMO 比愚蠢的、不必要的“嵌套”(带有额外的大括号)更令人愉悦。 2)“嵌套”和“堆叠”都生成相同的IL字节码。我们都同意 :) 3) 在一定程度上它不能解决警告(我说了这么多),我根本不会为愚蠢的“使用”而烦恼。我只是用一个(单个!)try/dispose 块来做所有事情。

以上是关于使用语句 FileStream 和/或 StreamReader - Visual Studio 2012 警告的主要内容,如果未能解决你的问题,请参考以下文章

使用语句 FileStream 和/或 StreamReader - Visual Studio 2012 警告

FileStream和StreamReader,StreamWrite,BinaryWriter

FileStream读写文件StreamWriter 和 StreamReader

FileStream和StreamreaderStreamWriter的区别

对FileStream的几种属性和方法认识

对FileStream的几种属性和方法认识