File Scoped Namespaces

Summary

File scoped namespaces use a less verbose format for the typical case of files containing only one namespace. The file scoped namespace format is namespace X.Y.Z; (note the semicolon and lack of braces). This allows for files like the following:

namespace X.Y.Z;

using System;

class X
{
}

The semantics are that using the namespace X.Y.Z; form is equivalent to writing namespace X.Y.Z { ... } where the remainder of the file following the file-scoped namespace is in the ... section of a standard namespace declaration.

Motivation

Analysis of the C# ecosystem shows that approximately 99.7% files are all of either one of these forms:

namespace X.Y.Z
{
    // usings

    // types
}

or

// usings

namespace X.Y.Z
{
    // types
}

However, both these forms force the user to indent the majority of their code and add a fair amount of ceremony for what is effectively a very basic concept. This affects clarity, uses horizontal and vertical space, and is often unsatisfying for users both used to C# and coming from other languages (which commonly have less ceremony here).

The primary goal of the feature therefore is to meet the needs of the majority of the ecosystem with less unnecessary boilerplate.

Detailed design

This proposal takes the form of a diff to the existing https://github.com/dotnet/csharplang/blob/main/spec/namespaces.md#compilation-units section of the specification.

Diff

A compilation_unit defines the overall structure of a source file. A compilation unit consists of zero or more using_directives followed by zero or more global_attributes followed by zero or more namespace_member_declarations.

A compilation_unit defines the overall structure of a source file. A compilation unit consists of zero or more using_directives followed by zero or more global_attributes followed by a compilation_unit_body. A compilation_unit_body can either be a file_scoped_namespace_declaration or zero or more statements and namespace_member_declarations.

compilation_unit
~~    : extern_alias_directive* using_directive* global_attributes? namespace_member_declaration*~~
    : extern_alias_directive* using_directive* global_attributes? compilation_unit_body
    ;

compilation_unit_body
    : statement* namespace_member_declaration*
    | file_scoped_namespace_declaration
    ;

... unchanged ...

A file_scoped_namespace_declaration will contribute members corresponding to the namespace_declaration it is semantically equivalent to. See (Namespace Declarations) for more details.

Namespace declarations

A namespace_declaration consists of the keyword namespace, followed by a namespace name and body, optionally followed by a semicolon. A file_scoped_namespace_declaration consists of the keyword namespace, followed by a namespace name, a semicolon and an optional list of extern_alias_directives, using_directives and type_declarations.

namespace_declaration
    : 'namespace' qualified_identifier namespace_body ';'?
    ;
    
file_scoped_namespace_declaration
    : 'namespace' qualified_identifier ';' extern_alias_directive* using_directive* type_declaration*
    ;

... unchanged ...

... unchanged ...

the two namespace declarations above contribute to the same declaration space, in this case declaring two classes with the fully qualified names N1.N2.A and N1.N2.B. Because the two declarations contribute to the same declaration space, it would have been an error if each contained a declaration of a member with the same name.

A file_scoped_namespace_declaration permits a namespace declaration to be written without the { ... } block. For example:

extern alias A;
namespace Name;
using B;
class C
{
}

is semantically equivalent to

extern alias A;
namespace Name
{
    using B;
    class C
    {
    }
}

Specifically, a file_scoped_namespace_declaration is treated the same as a namespace_declaration at the same location in the compilation_unit with the same qualified_identifier. The extern_alias_directives, using_directives and type_declarations of that file_scoped_namespace_declaration act as if they were declared in the same order inside the namespace_body of that namespace_declaration.

A source file cannot contain both a file_scoped_namespace_declaration and a namespace_declaration. A source file cannot contain multiple file_scoped_namespace_declarations. A compilation_unit cannot contain both a file_scoped_namespace_declaration and any top level statements. type_declarations cannot precede a file_scoped_namespace_declaration.

Extern aliases

... unchanged ...