Objects that implement IDisposable
are everywhere. The interface even gets its own language features (C#, VB, F#). However, LINQ throws a few wrenches into things:
- LINQ’s query syntax depends on expressions;
using
blocks are statements. - When querying a sequence of
IDisposable
objects, there’s no easy way to ensure disposal after each element has been consumed. - Returning deferred queries from within a
using
statement is often desired, but fails spectacularly.
There are possible work-arounds for each issue…
- Put the using statement in a method (named or anonymous) that is called from the query. See also: Thinking Functional: Using.
- Use a method that creates a dispose-safe iterator of the sequence, like AsSafeEnumerable().
- Refactor the method to inject the
IDisposable
dependency, as shown in the first part of Marc’s answer here.
But, as you might have guessed, I would like to propose a better solution. The code is really complex, so bear with me:
public static IEnumerable<T> Use<T>(this T obj) where T : IDisposable { try { yield return obj; } finally { if (obj != null) obj.Dispose(); } }
That’s it. We’re turning our IDisposable
object into a single-element sequence. The trick is that the C# compiler will build an iterator for us that properly handles the finally
clause, ensuring that our object will be disposed. It might be helpful to set a breakpoint on the finally
clause to get a better idea what’s happening.
So how can this simple method solve all our problems? First up: “using” a FileStream
object created in a LINQ query:
var lengths = from path in myFiles from fs in File.OpenRead(path).Use() select new { path, fs.Length };
Since the result of Use()
is a single-element sequence, we can think of from fs in something.Use()
as an assignment of that single value, something
, to fs
. In fact, it’s really quite similar to an F# use
binding in that it will automatically clean itself up when it goes out of scope (by its enumerator calling MoveNext()
).
Next, disposing elements from a collection. I’ll use the same SharePoint problem that AsSafeEnumerable()
solves:
var webs = from notDisposed in site.AllWebs from web in notDisposed.Use() Â select web.Title;
I find this syntax rather clumsy compared with AsSafeEnumerable()
, but it’s there if you need it.
Finally, let’s defer disposal of a LINQ to SQL DataContext
until after the deferred query is executed, as an answer to the previously-linked Stack Overflow question:
IQueryable<MyType> MyFunc(string myValue) { return from dc in new MyDataContext().Use() from row in dc.MyTable where row.MyField == myValue select row; } void UsingFunc() { var result = MyFunc("MyValue").OrderBy(row => row.SortOrder); foreach(var row in result) { //Do something } }
The result of MyFunc
now owns its destiny completely. It doesn’t depend on some potentially disposed DataContext
– it just creates one that it will dispose when it’s done. There are probably situations where you would want to share a DataContext
rather than create one on demand (I don’t use LINQ to SQL, I just blog about it), but again it’s there if you need it.
I’ve only started using this approach recently, so if you have any problems with it please share.
July 23, 2009 at 6:18 pm
[…] Using IDisposables with LINQ […]
August 6, 2009 at 12:54 pm
[…] recently posted an approach to dealing with IDisposable objects and LINQ. In the comments at LosTechies, Steve Gentile […]
August 6, 2009 at 12:58 pm
[…] recently posted an approach to dealing with IDisposable objects and LINQ. In the comments at LosTechies, Steve Gentile […]
September 15, 2009 at 3:04 am
[…] use various techniques to broaden our expressive reach. I have already documented one such hack for managing IDisposable objects with LINQ, so I guess we can call this the second in an unbounded […]
September 18, 2012 at 8:59 am
While adding some extra extension methods around this approach, I came across a scenario which results in failure to dispose of the objects… when using a short circuit LINQ method (Any, First, FirstOrDefault, etc)… the remaining objects in the initial array are not disposed… probably not an issue with the file example (since the disposal is only necessary if the file is being opened/processed)… but the AllWebs will materialize the entire resultset… so a short circuit would leave the remaining objects in memory.
I wrote an article focusing on unit tests, which happened to include your code : http://sbrickey.com/Tech/Blog/Post/More_good_examples_of_Mock_Objects
September 18, 2012 at 7:36 pm
I appreciate the analysis in your post! One thing I should have pointed out above is that using objects wrapped in
Use()
(orAsSafeEnumerable()
) is only safe within the context of enumeration over the sequence. UsingFirstOrDefault()
, for example, will return an instance that has already been disposed (while the rest of the sequence, ironically, won’t be).