Implicit using block

Topics: C# Language Design
May 9, 2014 at 9:02 AM
Edited May 9, 2014 at 9:13 AM
I would like to see a feature that is similar to F#'s use keyword.

Local variables could be declared with using keyword. This would define an implicit using block around the scope of the variable. See the following examples:
using var resource = new Resource();
resource.Action()

SAME AS

using (var resource = new Resource())
{
    resource.Action();
}
using var resource = new Resource();
using var wrapper = new Wrapper(resource)
wrapper.Action()

SAME AS

using (var resource = new Resource())
using (var wrapper = new Wrapper(resource))
{
    wrapper.Action();
}
using var resource = new Resource();
resource.Update();
{
    using var wrapper = new Wrapper(resource)
    wrapper.Action();
}
resource.Update();

SAME AS

using (var resource = new Resource())
{
    resource.Update();
    using (var wrapper = new Wrapper(resource))
    {
        wrapper.Action();
    }
    resource.Update();
}
May 9, 2014 at 9:43 AM
I'd go even further than that by the following
class Subscription : IDisposable {

    using IDisposable InnerResourceA;
    using IDisposable InnerResourceB;
    using IDisposable InnerResourceC;   

}
So then we would have explicit ownership semantics in the source code. The subscription class owns the inner resources and disposes of them. The using keyword at class level should automatically generate a public void Dispose() method to dispose of the owned resources.
May 9, 2014 at 3:52 PM
bradphelan wrote:
I'd go even further than that by the following
I'm not quite clear what the original syntax is supposed to do that the existing using statement does not. However, I've long wished for your second proposal.
So then we would have explicit ownership semantics in the source code. The subscription class owns the inner resources and disposes of them. The using keyword at class level should automatically generate a public void Dispose() method to dispose of the owned resources.
The biggest difficulty I see with your second proposal is that it's unclear how that should fit in with any other cleanup the class might require. My thought would be to specify that auto Dispose(); or auto Dispose(pendingException); should call IDisposable.Dispose or IDisposableExOnly.Dispose(Exception) on each using field, the compiler should auto-generate normal Dispose-pattern code if the class is declared as implementing IDisposable, IDisposableEx, or IDisposableExOnly and no other such code exists; otherwise it should squawk if using fields exist but there exists no call to auto Dispose() or auto Dispose(Exception).

(*) Defining IDisposableEx: IDisposable,IDisposableExOnly is a separate but related idea; if a new disposal syntax is added, adding IDisposableEx at the same time will be easier than adding it later. There are times where semantically-correct operation of cleanup code requires knowing whether it is being invoked in the course of normal code execution, or because of stack unwinding; the present IDisposable doesn't allow for that, but IDisposableEx and IDisposableExOnly would.
May 9, 2014 at 4:43 PM
Edited May 9, 2014 at 4:43 PM
My proposal is merely a syntax sugar that would make the code cleaner by removing the need of unnecessary blocks and indenting. See this more complex example:
using (var file = File.Open("path", FileMode.Create))
{
    file.Seek(5, SeekOrigin.Begin);

    using (var gzip = new GZipStream(file, CompressionMode.Compress))
    using (var writer = new StreamWriter(gzip))
    {
        var connectionFactory = new ConnectionFactory();

        using (var conn = connectionFactory.Create())
        {
            conn.Open();

            using (var command = conn.CreateCommand())
            {
                command.CommandText = "...";

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var line = reader.GetString(reader.GetOrdinal("name"));

                        writer.WriteLine(line);
                    }
                }
            }
        }
    }
}

BECOMES

using var file = File.Open("path", FileMode.Create));

file.Seek(5, SeekOrigin.Begin);

using var gzip = new GZipStream(file, CompressionMode.Compress);
using var writer = new StreamWriter(gzip);
    
var connectionFactory = new ConnectionFactory();

using var conn = connectionFactory.Create();
        
conn.Open();

using var command = conn.CreateCommand();
            
command.CommandText = "...";

using var reader = command.ExecuteReader();

while (reader.Read())
{
    var line = reader.GetString(reader.GetOrdinal("name"));
    writer.WriteLine(line);
}
May 9, 2014 at 5:28 PM
@tamasflamich I like this proposal. It not only makes it less verbose, but it makes it easier to write correct code without tripping all over yourself with nested using scopes and such.
May 9, 2014 at 7:06 PM
Edited May 9, 2014 at 7:08 PM
The braces associated with the existing using clearly indicate the scope of the variables and resources created thereby. In your original example, all resources are kept for the same duration, but that is generally not proper behavior. In the code:
void blah
{
  using(var foo = new biz())
  {
    biz.doSomething();
  }
  doSomethingElse();
}
if doSomethingElse doesn't use the guarded resources, they should be released before it runs; the only way for the compiler to know when they should be released is to have syntax indicating that. One of the marks of a good language is that it should avoid making it easier to do the wrong thing than the right one. Extending the lifetime of biz until after doSomethingElse would be the wrong thing to do, and I don't think a language should encourage it.
May 9, 2014 at 7:28 PM
It's true that it's possible to unintentionally hold on to resources longer this way, but it's not like the existing using statement ceases to exist. Often when I see people using using, the entire method body is wrapped in it. This makes those scenarios cleaner and also by having less verbosity, it makes it an easier decision to use IDisposable to ensure some code is called when a variable is no longer being used. (I know purists are against using IDisposable for anything but unmanaged resource disposal, but given its language support via using it's useful in many more situations than just that.)
May 9, 2014 at 10:40 PM
MgSam wrote:
It's true that it's possible to unintentionally hold on to resources longer this way, but it's not like the existing using statement ceases to exist. Often when I see people using using, the entire method body is wrapped in it.
There are many cases where that is appropriate, but that doesn't mean a compiler should encourage such behavior in cases where it is not important. If it is necessary to add code which belongs outside the using, but probably wouldn't matter too much if it was inside, then under the old syntax a programmer would not be discouraged from putting the code where it belongs. Under the new syntax, by contrast, a programmer who wanted to put the new code in the proper place would have to rewrite the using statement. Consequently, it would be unclear to anyone reading the code whether the programmer thought the new code belonged within the using statement, or was put there because putting it after would have been too much of a nuisance.

IMHO, there is considerable in knowing that the reason the precise semantics of a program are what they are is that the programmer wanted them that way, rather than because it would have been harder or impossible to express the semantics the programmer really wanted. Adding constructs to make it easier for a programmer to do the wrong thing than it would be to do the right thing undermines that.

BTW, if you saw the code:
float OneThird = 1.0f/3.0f;

float secondFloat = someFloat * OneThird;
double secondDouble = firstDouble * OneThird;
what would you guess about the reason firstDouble was multiplied by 0.3333333432674408 rather than 0.33333333333333331? Would you guess the programmer was multiply by the former value because it was the right thing to do, or because it was easier than having to use a cast every time a the program wanted to multiply a float by OneThird?
(I know purists are against using IDisposable for anything but unmanaged resource disposal, but given its language support via using it's useful in many more situations than just that.)
The purpose of IDisposable is to notify an object that it is no longer needed and may be abandoned without further notice, and to allow the object to make use of that information as it sees fit. As with other notification interfaces, the purpose is not to have the object to do something for the caller, but rather to give the object a chance to do something that may be necessary to uphold other contracts.
May 10, 2014 at 3:50 PM
One thing that I feel really painful of using blocks is when you want to return a local variable but you need to declare it first:
string fileContent;
using(var file = File.Open("myFile.txt"))
{
      fileContent = file.ReadAllText(); 
}

Console.WriteLine(fileContent)
Note: I'm aware of File.ReadAllText.

I like this feature because it will allow simple code in this case:
using var file = File.Open("myFile.txt"); // can we just skip var here? 
string fileContent = file.ReadAllText(); 
Console.WriteLine(fileContent)
In the meantime, I've written a helper method Using like this one:
[DebuggerStepThrough]
public static R Using<T, R>(this T disposable, Func<T, R> function)
    where T : IDisposable
{
    using (disposable)
        return function(disposable);
}
So I can write:
string fileContent = File.Open("myFile.txt").Using(file => file.ReadAllText());
Console.WriteLine(fileContent)
May 11, 2014 at 6:15 AM
Some personal opinion:
C# uses too many braces. We often use IDE extensions just to give hint about what a close brace is. Some braces could have been removed, like the using statement mentioned here, and namespace. It adds little value but increase indent levels, just making it more vebose and harder to read.

About the scope issue if using statement became braceless: That is easy, just extract a new function for the using which need to limit its scope smaller. This is the same as what we do today when using statement leads to deep indentions.

Warning: the following contains my rant.
In C++, each resource variable (using RAII) takes only one line, while in the intended-to-be-better lanuage, it takes 3 lines and add one level of indention.

Especially in enterprisy code, we need to follow many bull-shit style rules which are advocated by many people. E.g. braces must be always used even for single line code. There must be a space after a close brace. A code line must be no more than 80 characters. (Before I write any program logic, there are already 3 levels of indention and 12 characters out of 80 are already used.) A function which is either long or having too many indentions should be refactored into many small functions. etc.

All these just make it very vebose and space-wasting. Even equivalent VB code could be shorter and more readable.
May 11, 2014 at 3:01 PM
qrli wrote:
Especially in enterprisy code, we need to follow many bull-shit style rules which are advocated by many people. E.g. braces must be always used even for single line code. There must be a space after a close brace. A code line must be no more than 80 characters. (Before I write any program logic, there are already 3 levels of indention and 12 characters out of 80 are already used.) A function which is either long or having too many indentions should be refactored into many small functions. etc.
Two-space indents are just as usable as four-space, but take only half the horizontal space.

Otherwise, the use of braces in C-style languages is sufficiently well-established that anything else would violate the principle of least astonishment, but the supposedly-more-verbose VB.NET saves a vertical line with:
Using ThisVar =Whatever()
  Code using it
End Using
Otherwise, I'm not quite clear what the examples are supposed to illustrate. If it's acceptable to keep the file open during the Console.WriteLine the first example could be shortened--using existing syntax rules--to
using(var file = File.Open("myFile.txt"))
  Console.WriteLine(file.ReadAllText());
May 12, 2014 at 1:25 AM
Okay, when I first saw this proposal I thought it was kinda silly. But the RAII argument, that actually makes sense and I can see how a form of using which adheres to the current scope instead of defining its own can make sense, particularly for resources that don't need to be released at a specific moment.

As for the rant, I've never understood why having the opening brace on its own line became the defacto standard. I much prefer it to reside at the end of the previous line and to denote the scope more through indentation (with proper tabs!) than by the presence of the opening brace. This is especially true now with web projects combining C# and JavaScript since the latter will only display its horrid design if you always keep opening braces on their own line. It's also fun to have to deal with strict code formatting rules defined by some MBA-type manager who happened to stumble upon some etiquette book out of the 1960s and he's manically concerned with accidental over-punching.
May 12, 2014 at 4:47 AM
supercat wrote:
Two-space indents are just as usable as four-space, but take only half the horizontal space.
Sadly, 4 spaces indention is also the standard in enterprise code.
Otherwise, I'm not quite clear what the examples are supposed to illustrate. If it's acceptable to keep the file open during the Console.WriteLine the first example could be shortened--using existing syntax rules--to
using(var file = File.Open("myFile.txt"))
  Console.WriteLine(file.ReadAllText());
As said, braces are always required even for single line, as per those style rules.
May 12, 2014 at 1:42 PM
qrli wrote:
Sadly, 4 spaces indention is also the standard in enterprise code.
As said, braces are always required even for single line, as per those style rules.
The fact that a language already allows a more concise syntax but enterprise standards people would forbid it would suggest that adding the proposed new syntax would have the same result.