Using IDisposables with LINQ
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.