MoreLinq Acquire是什么?它有什么作用?

10

我正在检查Jon Skeet的MoreLinq,对Acquire扩展源代码产生了好奇。

实现如下所示

        /// <summary>
        /// Ensures that a source sequence of <see cref="IDisposable"/> 
        /// objects are all acquired successfully. If the acquisition of any 
        /// one <see cref="IDisposable"/> fails then those successfully 
        /// acquired till that point are disposed.
        /// </summary>
        /// <typeparam name="TSource">Type of elements in <paramref name="source"/> sequence.</typeparam>
        /// <param name="source">Source sequence of <see cref="IDisposable"/> objects.</param>
        /// <returns>
        /// Returns an array of all the acquired <see cref="IDisposable"/>
        /// object and in source order.
        /// </returns>
        /// <remarks>
        /// This operator executes immediately.
        /// </remarks>

        public static TSource[] Acquire<TSource>(this IEnumerable<TSource> source)
            where TSource : IDisposable
        {
            if (source == null) throw new ArgumentNullException("source");

            var disposables = new List<TSource>();
            try
            {
                disposables.AddRange(source);
                return disposables.ToArray();
            }
            catch
            {
                foreach (var disposable in disposables)
                    disposable.Dispose();
                throw;
            }
        }

据我理解,它接收一个 IEnumerable<IDisposable> 并创建一个 List<IDisposable>

但我无法领会其中可能出现的问题。

有人能向我解释一下,并可能提供一个使用该扩展的示例吗?


1
我认为Jon Skeet基于很明显的原因最适合回答这个问题。至于示例,可以说我有一个已关闭的文件描述符序列/数组/列表,并且我想打开它们并使用它们(我需要全部)。如果任何文件无法打开,我希望自动关闭所有其他描述符。在这种情况下,Acquire将完全做到这一点。 - Patryk Ćwiek
一个懒惰的可枚举对象可能会在迭代过程中抛出异常。这种情况下,您应该Dispose所有先前的对象,这个函数就是这样做的。 - CodesInChaos
所有的答案都很完美,我想将它们全部标记为“已接受”,但我只能选择一个。看到这些答案让我感觉像是“哦”。 - Luis Filipe
3个回答

16

AddRange的调用会遍历source。如果因为任何原因它遇到异常,之前获取的任何异常都将被处理。考虑以下示例:

var filenames = new[] { "file1.xml", "file2.xml", "doesnotexist.xml" };
var disposables = filenames.Select(fn => File.OpenRead(fn));
var fileStreams = disposables.Acquire();

由于懒加载,当您分配 "disposables" 时不会抛出任何异常。但是,在 Aquire 中的 AddRange 调用到达第三个元素(尝试打开 "doesnotexist.xml" 时),将引发 FileNotFoundException。发生这种情况时,Acquire 将安全地处理之前的流。简单的 ToList / ToArray 将使前两个文件流保持打开状态。
实质上,Acquire 的作用是确保要么安全打开 filenames 中的 所有 文件,要么不打开任何文件。

7
假设你有一段代码,逐个创建并返回可丢弃的对象:
public IEnumerable<FileStream> GetFiles()
{
    yield return File.OpenRead("file1");
    yield return File.OpenRead("file2"); // does not exist
    yield return File.OpenRead("file3");
}

您需要获取所有的一次性对象,但是如果在获取过程中出现异常,则已经获取的对象将保留在内存中而未被清除。因此,Acquire方法要么获取所有流并返回它们,要么在失败时处理已经获取的流并重新抛出异常。

FileStream[] streams = GetFiles().Acquire();

2

请记住,使用LINQ获取的大多数IEnumerable集合以延迟方式进行评估,例如,您只会获得生成列表的配方。实际执行代码仅在您迭代集合时才会发生,这在此处发生:disposables.AddRange(source)。如果此调用失败,则最终会得到一部分应该被处理的对象集合,这在此处发生:

            foreach (var disposable in disposables)
                disposable.Dispose();

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