แก้ไข

แชร์ผ่าน


"pattern-based using" and "using declarations"

Note

This article is a feature specification. The specification serves as the design document for the feature. It includes proposed specification changes, along with information needed during the design and development of the feature. These articles are published until the proposed spec changes are finalized and incorporated in the current ECMA specification.

There may be some discrepancies between the feature specification and the completed implementation. Those differences are captured in the pertinent language design meeting (LDM) notes.

You can learn more about the process for adopting feature speclets into the C# language standard in the article on the specifications.

Summary

The language will add two new capabilities around the using statement in order to make resource management simpler: using should recognize a disposable pattern in addition to IDisposable and add a using declaration to the language.

Motivation

The using statement is an effective tool for resource management today but it requires quite a bit of ceremony. Methods that have a number of resources to manage can get syntactically bogged down with a series of using statements. This syntax burden is enough that most coding style guidelines explicitly have an exception around braces for this scenario.

The using declaration removes much of the ceremony here and gets C# on par with other languages that include resource management blocks. Additionally the pattern-based using lets developers expand the set of types that can participate here. In many cases removing the need to create wrapper types that only exist to allow for a values use in a using statement.

Together these features allow developers to simplify and expand the scenarios where using can be applied.

Detailed Design

using declaration

The language will allow for using to be added to a local variable declaration. Such a declaration will have the same effect as declaring the variable in a using statement at the same location.

if (...) 
{ 
   using FileStream f = new FileStream(@"C:\users\jaredpar\using.md");
   // statements
}

// Equivalent to 
if (...) 
{ 
   using (FileStream f = new FileStream(@"C:\users\jaredpar\using.md")) 
   {
    // statements
   }
}

The lifetime of a using local will extend to the end of the scope in which it is declared. The using locals will then be disposed in the reverse order in which they are declared.

{ 
    using var f1 = new FileStream("...");
    using var f2 = new FileStream("..."), f3 = new FileStream("...");
    ...
    // Dispose f3
    // Dispose f2 
    // Dispose f1
}

There are no restrictions around goto, or any other control flow construct in the face of a using declaration. Instead the code acts just as it would for the equivalent using statement:

{
    using var f1 = new FileStream("...");
  target:
    using var f2 = new FileStream("...");
    if (someCondition) 
    {
        // Causes f2 to be disposed but has no effect on f1
        goto target;
    }
}

A local declared in a using local declaration will be implicitly read-only. This matches the behavior of locals declared in a using statement.

The language grammar for using declarations will be the following:

local-using-declaration:
  'using' type using-declarators

using-declarators:
  using-declarator
  using-declarators , using-declarator
  
using-declarator:
  identifier = expression

Restrictions around using declaration:

  • May not appear directly inside a case label but instead must be within a block inside the case label.
  • May not appear as part of an out variable declaration.
  • Must have an initializer for each declarator.
  • The local type must be implicitly convertible to IDisposable or fulfill the using pattern.

pattern-based using

The language will add the notion of a disposable pattern for ref struct types: that is a ref struct which has an accessible Dispose instance method. Types which fit the disposable pattern can participate in a using statement or declaration without being required to implement IDisposable.

ref struct Resource
{ 
    public void Dispose() { ... }
}

using (var r = new Resource())
{
    // statements
}

This will allow developers to leverage using for ref struct types. These types can't implement interfaces today and hence can't participate in using statements.

The same restrictions from a traditional using statement apply here as well: local variables declared in the using are read-only, a null value will not cause an exception to be thrown, etc ... The code generation will be different only in that there will not be a cast to IDisposable before calling Dispose:

{
	  Resource r = new Resource();
	  try {
		    // statements
	  }
	  finally {
		    if (r != null) r.Dispose();
	  }
}

In order to fit the disposable pattern the Dispose method must be an accessible instance member, parameterless and have a void return type. It cannot be an extension method.

Considerations

case labels without blocks

A using declaration is illegal directly inside a case label due to complications around its actual lifetime. One potential solution is to simply give it the same lifetime as an out var in the same location. It was deemed the extra complexity to the feature implementation and the ease of the work around (just add a block to the case label) didn't justify taking this route.

Future Expansions

fixed locals

A fixed statement has all of the properties of using statements that motivated the ability to have using locals. Consideration should be given to extending this feature to fixed locals as well. The lifetime and ordering rules should apply equally well for using and fixed here.