NoUsingDeclaration

NoUsingDeclaration

Summary

Use Using Declarations to declare a local variable whenever possible.

Default severity

Warning

Description

When you declare a local variable and initialize it with an IDisposable object, you should use the Using Declaration [1] whenever possible as follows:

// For implicitly-typed variables
using var reader = new StreamReader("file.txt");

// For explicitly-typed variables
using StreamReader i = new("file.txt");

// Multiple declarators are allowed in explicit type declarations
using StreamReader j = new("1.txt"), k = new("2.txt");

The Using Declaration is preferred to the Using Statement or the try-finally idiom because it is easier to describe RAII in C#.

Remarks

This analyzer only triggers diagnostics if the local variable is declared and initialized with the new operator. It ignores the declaration of the local variable that is initialized with an IDisposable instance any function or property returns, as follows:

// OK
var out = System.Console.Out;

static TextReader NewStreamReader() => new StreamReader("file.txt");

// OK
var reader = NewStreamReader();

Initialization with an IDisposable instance created with an operator other than the new operator is also not covered as follows:

// OK (but you should prevent resource leaks)
var reader = (inputFile is null)
    ? new StringReader(defaultText)
    : new StreamReader(inputFile); 

Cases where no diagnosis is issued

This analyzer should not raise a diagnostic when a factory method creates and returns an IDisposable object as follows:

public Socket? NewTcpSocket(Uri uri, int port)
{
    // XXX (you cannot use Using Declaration)
    var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
    try
    {
        socket.Connect(uri.Host, port);
        return socket;
    }
    catch (Exception)
    {
        socket.Dispose();
    }
    return null;
}

Similarly, this analyzer should not raise a diagnostic if you instantiate an IDisposable object and assign it to a field or property or capture it as follows:

private StreamReader? streamReader;

private StreamWriter? SharedWriter { get; set; }

public void PrepareStream()
{
    // XXX (you cannot use Using Declaration)
    var reader = new StreamReader("input.txt");
    streamReader = reader;
    var writer = new StreamWriter("output.txt");
    SharedWriter = writer;
    ⋮
}

public static Action WriteHelloAction()
{
    // XXX (you cannot use Using Declaration)
    var writer = new StreamWriter("file.txt");
    return () =>
    {
        // How do you dispose of it!?
        writer.WriteLine("hello");
    };
}

This analyzer should still not raise a diagnostic in the following complex case where you instantiate an IDisposable object wrapped in a decorator pattern:

public BufferedStream NewClientStream(…)
{
    // XXX (you cannot use Using Declaration)
    var clientSocket = new Socket(
        AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    clientSocket.Connect(…);
    var netStream = new NetworkStream(clientSocket, true);
    var bufStream = new BufferedStream(netStream, streamBufferSize);
    return bufStream;
}

It also does not issue a diagnostic when you reassign any value to the variable as follows:

// XXX (Using Declaration causes an error CS1656 at the line /*💀*/.)
var i = new StreamReader("file.txt");
Console.WriteLine(i.ReadLine());
/*💀*/ i = new StreamReader("another.txt");
Console.WriteLine(i.ReadLine());

In summary, this analyzer will not issue diagnostics if the variable representing the created instance is:

  • used as a parameter of a method or constructor
  • on the right side of the assignment expression
  • captured
  • returned
  • reassigned

Therefore, you should be aware that resource leaks can occur even though this analyzer does not issue any diagnostics.

Classes whose dispose() method does nothing

The local variables whose type is one of the following are not covered:

See also UnnecessaryUsing analyzer.

For example:

// OK
var reader = new StringReader("hello");

// OK
StringReader i = new("hello");

However, even if the concrete class of the instance is one of those above, it is covered if the type of the variable is not so, as follows:

// NG
TextReader i = new StringReader("hello");

Code fix

The code fix introduces an option to add a using keyword before var or the type name.

Example

Diagnostic

public sealed class Code
{
    public static void Main()
    {
        var i = new StreamReader("file.txt");
        StreamReader j = new("file.txt");
    }
}

Code fix

public sealed class Code
{
    public static void Main()
    {
        using var i = new StreamReader("file.txt");
        using StreamReader j = new("file.txt");
    }
}

References

[1] Microsoft, "pattern-based using" and "using declarations"