我正在检查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;
}
}
根据我的理解,它会收到IEnumerable<IDisposable>
并创建List<IDisposable>
。
我无法理解这里可能出现的问题。
任何人都可以向我解释,并可能提供一个示例,其中此扩展可能有用吗?
答案 0 :(得分:15)
对AddRange
的调用会重复source
。如果出于任何原因遇到异常,则先前获得的任何异常将被处置。考虑这个例子:
var filenames = new[] { "file1.xml", "file2.xml", "doesnotexist.xml" };
var disposables = filenames.Select(fn => File.OpenRead(fn));
var fileStreams = disposables.Acquire();
由于懒惰的评估,当您分配disposables
时,不会抛出任何异常。但是,当AddRange
内Aquire
的调用到达第三个元素(尝试打开"doesnotexist.xml"
)时,将抛出FileNotFoundException
。发生这种情况时,Acquire
将安全地处理以前的流。一个简单的ToList
/ ToArray
会使前两个文件流保持打开状态。
从本质上讲,Acquire
可以确保filenames
中的 all 文件安全地打开,或者没有任何文件。
答案 1 :(得分: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();
答案 2 :(得分:2)
请记住,使用LINQ获得的大多数IEnumerable
集合都是以懒惰的方式进行评估,例如:你只需要配方如何生成列表。只有在遍历集合时才会实际执行代码,在这种情况下,代码在disposables.AddRange(source)
中发生。如果此调用失败,那么您最终会得到应该处理的部分对象集合,其发生在此处:
foreach (var disposable in disposables)
disposable.Dispose();