Don’t mix using statements and lambda expressions


Title pretty much says it all but what good is a rule without any explanation.  The main issue here is that at the core, using statements and lambda expressions both alter variable lifetimes.  Unfortunately they alter the lifetime in different directions.  Using will shorten the life time of a variable to the specified block.  This is a somewhat artificial way because the object is still technically alive but can’t be trusted to do anything.  Lambda expressions take a variable limited to a specific scope and extends their lifetime to potentially be that of a heap value. Anytime two features alter the attribute of a variable in different directions, they can probably cause problems when used in conjunction. 


Take the following contrived but real example. 

        static Future<int> Example() {
using (var obj = new MyDisosableObject()) {
return Future.Create(() => obj.SomeFunction());
}
}

Comments (15)

  1. Lambda expressions are really cool, but they present some interesting problems because they aren’t really

  2. Joe Enos says:

    Never would have thought of that one…Thanks.

  3. zproxy says:

    The program does what you say it should to. It looks like the programmer clearly does not understand when is what executed. By simply moving the using statement inside the delegate it will be fixed…

  4. Eddie Garmon says:

    You need to clarify your blanket rule. It is perfectly fine to use lambdas inside a using block. I believe what you meant to say is: Don’t take any lifetime dependencies upon the object being ‘used’, and return that object as a lifted variable in a lambda function.  

    Below is a contrived but perfectly valid example of a lambda inside a using.

    static void Example() {

     int[] numbers = { 1, 2, 3, 4, 5 };

     using (var obj = new MyDisosableObject()) {

       numbers.ForEach(i => Console.WriteLine("{0} squared is {1}", i, i * i));

     }

    }

  5. General Lessons That I Learned From Waegis : Keyvan Nayyeri shares the lessons he learned from launching Waegis , a spam filter for web sites. Don’t Mix Using Statements And Lambda Expressions : Jared Parsons explains why you do not want to mix using

  6. jaredpar says:

    @Eddie,

    There are definately cases where this is valid and useful.  The issue I was refering to can be better summed up as "Don’t use lamda expressions which do both of the following"

    1) Lambda extends the lifetime of a lifted variable beyond the current function

    2) Lifted variable is iteration or using variable.

    There are probably other cases in #2 that I’m missing.  

    In general though I think it’s better to have a more encompasing rule which disallows certain valid cases than a rule which is less encompassing and allows invalid cases.  I’m a bit paranoid though and I’ve been bitten by the iteration variable before so I tend to be picky about this.  

  7. Eddie Garmon says:

    Ok, I do agree with the above. I had to go back and revalidate my Synchronized<T> as you made me think, (which is the point of this right?) as I do both of the above, but not at the same time.

  8. Ted says:

    Lambda expressions appear to be just syntactic sugar apart from querying a SQL server database.

  9. Orlando says:

    Jared I must say this is strange behavior. Shouldn’t the using statement be able to override the scope extension of the lambda expression instead of allowing this inconsistent behavior?

  10. jaredpar says:

    @Orlando,

    I think you mean issue a compiler error because of the scope change.  If so in an ideal world this should be the case.  However it is not done at this time for a couple of reasons.

    The first is cost.  For the compiler to accurately spot this as an error it must determine that the lambda will actually live beyond the scope of the current function.  This is very expensive (if not impossible).  In this case the compiler would have to dig into the IL of every function the lambda was either passed to or called.  The analysis would include deep flow analysis to determine if the lambda was ever stored in a place that was eventually returned from the function or passed to a different thread or even to PInvoke.  This is very costly in both man hours to implement and compile time.  

    The other is correctness.  Even though it’s against design guidlines, there is nothing stopping you from using an object once it’s been disposed.  You can in fact (please don’t though) design an object to have a subset of it’s properties/methods be valid after it’s disposed.  In this case the code I laid out would still be correct so the compiler would be issuing a false warning.  

  11. Kyle Sluder says:

    I think it makes sense to do this:

    func<IDisposable, string> f = x => { using(x) { x.ToString() } };

    This certainly can’t be a universally Bad Idea(TM), can it?

  12. James Hart says:

    LINQ expressions also introduce lambdas, less explicitly, and are particularly vulnerable to this sort of thing.

    IEnumerable<string> Names

    {

     get {

       using (var conn = new SqlConnection(ConnectionString))

       using (var cmd = new SqlCommand("SELECT Name FROM People", conn))

       {

         conn.Open();

         return

           from row in cmd.ExecuteReader().Cast<IDataRecord>()

           select row["Name"] as string;

       }

     }

    }

    But then again, the same problem occurs if you just do this:

    SqlDataReader Names

    {

     get {

       using (var conn = new SqlConnection(ConnectionString))

       using (var cmd = new SqlCommand("SELECT Name FROM People", conn))

       {

         conn.Open();

         return cmd.ExecuteReader();

       }

     }

    }

    So it’s really just another case of the danger of returning a deferred-execution object from within a using statement, which can be done with many things, such as a DataReader, an enumerator, a stream, or a lambda.

    Different bugs can be caused by passing out such objects from inside a lock {} block, too.

  13. Tanveer Badar says:

    I think it is the C# version of this C++ code

    int* dummy( )

    {

        int d = 0;

        return &d;

    }

  14. General Lessons That I Learned From Waegis : Keyvan Nayyeri shares the lessons he learned from launching Waegis , a spam filter for web sites. Don’t Mix Using Statements And Lambda Expressions : Jared Parsons explains why you do not want to mix using