take (0) on File.ReadLines似乎没有处理/关闭文件句柄

本文关键字:处理 文件句柄 on ReadLines File take | 更新日期: 2023-09-27 18:04:00

我有一个函数,它跳过n行代码,并使用File.ReadLinesSkipTake组合从给定文件中获取y行。当我试图打开filePath给出的文件下次:

string[] Lines = File.ReadLines(filePath).Skip(0).Take(0).ToArray();
using (StreamWriter streamWriter = new StreamWriter(filePath))
{
    // ...
}

我在"using"行上得到File in use by another process异常。

看起来IEnumerable.Take(0)是罪魁祸首,因为它返回一个空的IEnumerable而没有枚举File.ReadLines()返回的对象,我认为CC_11没有处理文件。

我说的对吗?他们不应该列举来避免这种错误吗?如何正确地做到这一点?

take (0) on File.ReadLines似乎没有处理/关闭文件句柄

这基本上是File.ReadLines的错误,而不是TakeReadLines返回IEnumerable<T>,这在逻辑上应该是惰性的,但是它急切地打开文件。除非对返回值进行迭代,否则没有什么可处理的。

也是只迭代一次。例如,您应该能够这样写:

var lines = File.ReadLines("text.txt");
var query = from line1 in lines
            from line2 in lines
            select line1 + line2;

…这样就得到了文件中直线的外积。它不能,因为它破碎了。

File.ReadLines 应该这样实现:

public static IEnumerable<string> ReadLines(string filename)
{
    return ReadLines(() => File.OpenText(filename));
}
private static IEnumerable<string> ReadLines(Func<TextReader> readerProvider)
{
    using (var reader = readerProvider())
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

不幸的是,它不是:(

选项:

  • 用上面的代替File.ReadLines
  • 编写自己的Take实现,使总是开始迭代,例如

    public static IEnumerable<T> Take<T>(this IEnumerable<T> source, int count)
    {
        // TODO: Argument validation
        using (var iterator = source.GetEnumerator())
        {
            while (count > 0 && iterator.MoveNext())
            {
                count--;
                yield return iterator.Current;
            }
        }
    }
    

从参考源代码File.ReadLines()上面的注释来看,很明显负责的团队知道关于这个"bug":

无法更改以保持与4.0兼容的已知问题:

  • 底层StreamReader被预先分配给之前的IEnumerable<T>甚至还调用了GetEnumerator。虽然这在例外情况下是好的,如直接抛出DirectoryNotFoundExceptionFileNotFoundExceptionFile.ReadLines(这可能是用户期望的),它也意味着阅读器如果用户从未在可枚举对象(因此.

所以他们希望File.ReadLines()在传递无效或不可读的路径时立即抛出,而不是在枚举时抛出。

替代方法很简单:不调用Take(0),或者如果对文件的内容不感兴趣,则不读取整个文件。

在我看来,根本原因是如果count为零,Enumerable.Take迭代器不会处理底层迭代器,因为代码没有进入foreach循环-请参阅参考资料。如果按以下方式修改代码,问题就解决了:

static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
    foreach (TSource element in source)
    {
        if (--count < 0) break;
        yield return element;
    }
}