Read moreLinq acquire. What does it do?

I studied Jon Skeet MoreLinq and I was curious to get the extension source code

The implementation is as follows.

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

In my understanding, it gets an IEnumerable<IDisposable> and creates a List<IDisposable> .

I can’t understand what could go wrong here.

Can someone explain this to me and perhaps give an example where this extension can be useful?

+6
source share
3 answers

A call to AddRange is performed with source repeating. If for any reason he encounters an exception, any that was previously acquired will be deleted. Consider this example:

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

No exception will be thrown if you assign disposables due to lazy pricing. However, when the AddRange call inside Aquire reaches the third element (where it tries to open "doesnotexist.xml" ), a FileNotFoundException will be FileNotFoundException . When this happens, Acquire will safely delete previous threads. A simple ToList / ToArray will leave the first two files open.

In essence, Acquire exists to ensure that either all files in filenames safe to open, or none of them.

+10
source

Suppose you have code that creates and returns one-time objects one at a time:

 public IEnumerable<FileStream> GetFiles() { yield return File.OpenRead("file1"); yield return File.OpenRead("file2"); // does not exist yield return File.OpenRead("file3"); } 

You need to get all disposable objects, but if there is an exception in the middle of the acquisition, then the objects that have already been received will remain in memory and will not be deleted. Thus, Acquire either acquires all the streams and returns them, or, after a failure, it disposes of all the streams already received and throws an exception.

 FileStream[] streams = GetFiles().Acquire(); 
+6
source

Remember that most IEnumerable collections that you use with LINQ are evaluated in a lazy way, for example. you only get the recipe for creating a list. The code actually only executes when you iterate over the collection, which in this case happens in disposables.AddRange(source) . If this call fails, you will get a partial set of objects that should be deleted:

  foreach (var disposable in disposables) disposable.Dispose(); 
+2
source

All Articles