10

我正在检查 Jon Skeet 的 MoreLinq,我对获取扩展源代码感到好奇

实现如下

        /// <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;
            }
        }

据我了解,它接收 aIEnumerable<IDisposable>并创建一个List<IDisposable>.

我无法掌握这里可能出现的任何问题。

谁能向我解释一下,并可能提供一个这个扩展有用的例子?

4

3 回答 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由于延迟评估,分配时不会引发异常。然而,当对AddRangeinside的调用Aquire到达第三个元素(它试图打开的地方"doesnotexist.xml")时,aFileNotFoundException将被抛出。发生这种情况时,Acquire将安全地处理以前的流。一个简单的ToList/ToArray将使前两个文件流保持打开状态。

从本质上讲,Acquire是否可以确保安全打开其中的所有文件filenames,或者一个都没有。

于 2014-01-31T15:01:31.960 回答
6

假设您有代码可以一一创建和返回一次性对象:

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();
于 2014-01-31T15:02:31.960 回答
2

请记住,IEnumerable您使用 LINQ 获得的大多数集合都是以惰性方式评估的,例如,您只获得了如何生成列表的方法。只有当您迭代集合时,代码才会真正执行,在这种情况下,发生在disposables.AddRange(source). 如果此调用失败,那么您最终会得到应处置的部分对象集合,这会发生在这里:

            foreach (var disposable in disposables)
                disposable.Dispose();
于 2014-01-31T15:02:25.120 回答