7 Basic concepts

7.1 Application startup and termination

7.1.1 General

A program may be compiled either as a class library to be used as part of other applications, or as an application that may be started directly. The mechanism for determining this mode of compilation is implementation-defined and external to this specification.

When an application is run, a new application domain is created. Several different instantiations of an application may exist on the same machine at the same time, and each has its own application domain. An application domain enables application isolation by acting as a container for application state. An application domain acts as a container and boundary for the types defined in the application and the class libraries it uses. Types loaded into one application domain are distinct from the same types loaded into another application domain, and instances of objects are not directly shared between application domains. For instance, each application domain has its own copy of static variables for these types, and a static constructor for a type is run at most once per application domain. Implementations are free to provide implementation-defined policy or mechanisms for the creation and destruction of application domains.

An application run is initiated by invoking an entry point which is selected at compile-time from candidate entry points which are specified in any of the following ways:

  1. Explicitly, by declaring a method with appropriate characteristics (§7.1.2).
  2. Implicitly, by using top-level statements (§7.1.3).
  3. Implementation-defined, by using a mechanism external to this specification to designate the entry point (§7.1.4).

Note: With the exception of top-level statement candidates (§7.1.3) all candidate entry points are standard methods; being a candidate, or the selected entry point, does not prevent these methods being invoked as normal. end note

It is a compile-time error if there are no candidate entry points.

Otherwise one candidate is selected at compile-time as the entry point following the process in §7.1.5.

An application is run by invoking the selected entry point as described in §7.1.6.

The termination status code, the purpose of which is to allow communication of success or failure to the host environment, is determined based on the result from the entry point invocation (§7.1.6).

7.1.2 Named entry points

A method qualifies as a canditate entry point by satisfying the following requirements:

  • It shall have the name Main.
  • It shall be static.
  • It shall not be generic.
  • It shall be declared in a non-generic type. If the type declaring the method is a nested type, none of its enclosing types may be generic.
  • The return type shall be void, int, System.Threading.Tasks.Task, or System.Threading.Tasks.Task<int>.
  • It may have the async modifier only if the method’s return type is System.Threading.Tasks.Task or System.Threading.Tasks.Task<int>.
  • It shall not be an optional partial method (§15.6.9) without an implementing declaration.
  • The parameter list shall either be empty, or have a single value parameter of type string[].

The declared accessibility (§7.4.2) of a method is ignored for the purposes of qualifying as a candidate entry point. At application startup the selected entry point is invoked regardless of its declared accessibility. However any declared accessibility continues to apply if the selected candidate entry point is invoked after application startup.

Note: The requirements here do not specify that Main should be a class member, it can be a struct member. end note

7.1.3 Using top-level statements

A single compilation unit (§14.2 in an application may contain a statement_list; called the top-level statements. The meaning of the top-level statements is semantically equivalent to declaring the following in the global namespace:

partial class Program
{
    static «AsyncAndReturnType» «Main»(string[] args)
    {
        «statement_list»
    }
}

The class Program is a partial type declaration (§15.2.7) which is combined with any other partial class declarations for Program within the application.

Note: This means that a top-level statement has the same rights of access as a statement in the body of a static method defined in the class Program in the global namespace. end note

The method name «Main» is a placeholder for an unspecified implementation provided name which is directly accessible only to the execution environment and cannot be referenced directly from within the application. Further there is no requirement that the name used is valid as a C# method name.

Note: However a representation of the name used can be obtained as a string during execution of a top-level statement if it invokes a method which uses the CallerMemberName attribute (§23.5.6.4). end note

The «statement_list» is a placeholder for statement_list of the compilation-unit.

The parameter args is in scope within the top-level statements and not otherwise. Regular name conflict/shadowing rules apply. This parameter receives the application parameters (§7.1.6).

Async operations are allowed in top-level statements to the degree they are allowed in statements within a named async entry-point method.

The replacement of the «AsyncAndReturnType» placeholder in the method signature is determined based on content of the top-level statements, as follows:

Top-level statements contain Generated signature
No await or return with value static void «Main»(string[] args)
return with value only static int «Main»(string[] args)
await only static async Task «Main»(string[] args)
await and return with value static async Task<int> «Main»(string[] args)

These generated signatures meet the requirements for named entry points (§7.1.2) and therefore «Main» is a candidate entry point.

Note: These signatures include no declared accessibility (§7.4.2). However an implementation may include declared accessibility – as accessibility is ignored for entry point methods (§7.1.2) the method would still be semantically equivalent.

7.1.4 Externally defined entry point

An implementation may provide a mechanism external to this specification to specify a candidate entry point.

If that mechanism identifies a type within the program then the candidate entry point is determined as specified for named entry points (§7.1.2).

If that mechanism identifies a method within the program then the method may have any name but otherwise must meet the requirements specified for named entry points (§7.1.2).

The external mechanism may only specify a single candidate entry point.

A candidate entry point specified by an external mechanism takes precedence in entry point selection (§7.1.5).

7.1.5 Entry point selection

It is a compile-time error if there are no candidate entry points.

Otherwise the entry point is selected from the candidates:

  • If one of the candidates is specified by an external mechanism (§7.1.4) it is selected as the entry point;
  • Otherwise, iff one of the candidates is defined by top-level statements (§7.1.3) it is selected as the entry point;
  • Otherwise:
    • If any of the candidates have a return type of int or void then any candidates having a return type of System.Threading.Tasks.Task or System.Threading.Tasks.Task<int> are removed from the candidate pool.
    • If there is a single remaining candidate it is selected as the entry point.
    • Otherwise the entry point cannot be determined and a compile-time error shall be reported.

The entry point selected at compile-time is invoked at runtime as part of application startup (§7.1.6).

7.1.6 Entry point invocation

If the entry point declares a parameter, then the implementation shall as the initial value of that parameter provide a non-null reference to a string array. This array shall consist of non-null references to zero or more strings, called application parameters, which are given implementation-defined values by the host environment prior to application startup.

Note: On systems supporting a command line, application parameters correspond to what are generally known as command-line arguments. end note

The application startup and termination process is semantically equivalent to the following steps:

  • An application run is started by either:
    • Invoking (§12.8.10) the entry-point method, if its return type is void or int; or
    • Awaiting (§12.9.9) the result of invoking the entry-point method, if its return type is a Task type.
    • In either case if the entry point requires an argument the application parameter array is supplied as its value.

Note: Invoking the entry-point method will cause the static constructor, if any, of the enclosing type to be executed first (§15.12, §16.8.10). end note

  • The application is terminated
    • If the run results in an int value it serves as the termination status code;
    • Otherwise, if the run results in no return value the termination status code is 0;
    • Otherwise, if the run terminates due to an exception (§22.4), the exit code is implementation-defined. Additionally, the implementation may provide alternative APIs for specifying the exit code.

Whether or not finalizers (§15.13) are run as part of application termination is implementation-defined.

Note: The .NET Framework implementation makes every reasonable effort to call finalizers (§15.13) for all of its objects that have not yet been garbage collected, unless such cleanup has been suppressed (by a call to the library method GC.SuppressFinalize, for example). end note

7.2 Declarations

Declarations in a C# program define the constituent elements of the program. C# programs are organized using namespaces. These are introduced using namespace declarations (§14), which can contain type declarations and nested namespace declarations. Type declarations (§14.8) are used to define classes (§15), structs (§16), interfaces (§19), enums (§20), and delegates (§21). The kinds of members permitted in a type declaration depend on the form of the type declaration. For instance, class declarations can contain declarations for constants (§15.4), fields (§15.5), methods (§15.6), properties (§15.7), events (§15.8), indexers (§15.9), operators (§15.10), instance constructors (§15.11), static constructors (§15.12), finalizers (§15.13), and nested types (§15.3.9).

A declaration defines a name in the declaration space to which the declaration belongs. It is a compile-time error to have two or more declarations that introduce members with the same name in a declaration space, except in the following cases:

  • Two or more namespace declarations with the same name are allowed in the same declaration space. Such namespace declarations are aggregated to form a single logical namespace and share a single declaration space.
  • Declarations in separate programs but in the same namespace declaration space are allowed to share the same name.

    Note: However, these declarations could introduce ambiguities if included in the same application. end note

  • Two or more methods with the same name but distinct signatures are allowed in the same declaration space (§7.5).
  • Two or more type declarations with the same name but distinct numbers of type parameters are allowed in the same declaration space (§7.7.2).
  • Two or more type declarations with the partial modifier in the same declaration space may share the same name, same number of type parameters and same classification (class, struct or interface). In this case, the type declarations contribute to a single type and are themselves aggregated to form a single declaration space (§15.2.7).
  • A namespace declaration and a type declaration in the same declaration space can share the same name as long as the type declaration has at least one type parameter (§7.7.2).

There are several different types of declaration spaces, as described in the following.

  • Within all compilation units of a program, namespace_member_declarations with no enclosing namespace_declaration are members of a single combined declaration space called the global declaration space.
  • Within all compilation units of a program, namespace_member_declarations within namespace_declarations that have the same fully qualified namespace name are members of a single combined declaration space. Per §14.3, this includes file_scoped_namespace_declarations.
  • Each compilation_unit and namespace_body has an alias declaration space. Each extern_alias_directive and using_alias_directive of the compilation_unit or namespace_body contributes a member to the alias declaration space (§14.6.2).
  • Each non-partial class, struct, or interface declaration creates a new declaration space. Each partial class, struct, or interface declaration contributes to a declaration space shared by all matching parts in the same program (§16.2.4). Names are introduced into this declaration space through class_member_declarations, struct_member_declarations, interface_member_declarations, or type_parameters. Except for overloaded instance constructor declarations and static constructor declarations, a class, struct, or interface cannot contain a member declaration with the same name as the class, struct, or interface. A class, struct, or interface permits the declaration of overloaded methods and indexers. Furthermore, a class or struct permits the declaration of overloaded instance constructors and operators. For example, a class, struct, or interface may contain multiple method declarations with the same name, provided these method declarations differ in their signature (§7.5). Note that base classes do not contribute to the declaration space of a class, and base interfaces do not contribute to the declaration space of an interface. Thus, a derived class or interface is allowed to declare a member with the same name as an inherited member. Such a member is said to hide the inherited member.
  • Each delegate declaration creates a new declaration space. Names are introduced into this declaration space through parameters (fixed_parameters and parameter_collections) and type_parameters.
  • Each enumeration declaration creates a new declaration space. Names are introduced into this declaration space through enum_member_declarations.
  • Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration, anonymous function, and local function creates a new declaration space called a local variable declaration space. Names are introduced into this declaration space through parameters (fixed_parameters and parameter_collections) and type_parameters. The set and init accessor for a property or an indexer introduce the name value as a parameter. The body of the function member, anonymous function, or local function, if any, is considered to be nested within the local variable declaration space. When a local variable declaration space and a nested local variable declaration space contain elements with the same name, within the scope of the nested local name, the outer local name is hidden (§7.6.1) by the nested local name.

    Note: Discard parameters of anonymous functions (§12.22.2) do not introduce names into any declaration space. end note

  • Additional local variable declaration spaces may occur within member declarations, anonymous functions and local functions. Names are introduced into these declaration spaces through patterns, declaration_expressions, declaration_statements and exception_specifiers. Local variable declaration spaces may be nested, but it is an error for a local variable declaration space and a nested local variable declaration space to contain elements with the same name. Thus, within a nested declaration space it is not possible to declare a local variable, local function or constant with the same name as a parameter, type parameter, local variable, local function or constant in an enclosing declaration space. It is possible for two declaration spaces to contain elements with the same name as long as neither declaration space contains the other. Local declaration spaces are created by the following constructs:
    • Each variable_initializer in a field and property declaration introduces its own local variable declaration space, that is not nested within any other local variable declaration space.
    • The body of a function member, anonymous function, or local function, if any, creates a local variable declaration space that is considered to be nested within the function’s local variable declaration space.
    • Each constructor_initializer creates a local variable declaration space nested within the instance constructor declaration. The local variable declaration space for the constructor body is in turn nested within this local variable declaration space.
    • Each block, switch_block, specific_catch_clause, iteration_statement and using_statement creates a nested local variable declaration space.
    • Each embedded_statement that is not directly part of a statement_list creates a nested local variable declaration space.
    • Each switch_section creates a nested local variable declaration space. However, variables declared directly within the statement_list of the switch_section (but not within a nested local variable declaration space inside the statement_list) are added directly to the local variable declaration space of the enclosing switch_block, instead of that of the switch_section.
    • The syntactic translation of a query_expression (§12.23.3) may introduce one or more lambda expressions. As anonymous functions, each of these creates a local variable declaration space as described above.
  • Each block or switch_block creates a separate declaration space for labels. Names are introduced into this declaration space through labeled_statements, and the names are referenced through goto_statements. The label declaration space of a block includes any nested blocks. Thus, within a nested block it is not possible to declare a label with the same name as a label in an enclosing block.

Note: The fact that variables declared directly within a switch_section are added to the local variable declaration space of the switch_block instead of the switch_section can lead to surprising code. In the example below, the local variable y is in scope within the switch section for the default case, despite the declaration appearing in the switch section for case 0. The local variable z is not in scope within the switch section for the default case, as it is introduced in the local variable declaration space for the switch section in which the declaration occurs.

int x = 1;
switch (x)
{
    case 0:
        int y;
        break;
    case var z when z < 10:
        break;
    default:
        y = 10;
        // Valid: y is in scope
        Console.WriteLine(x + y);
        // Invalid: z is not scope
        Console.WriteLine(x + z);
        break;
}

end note

The textual order in which names are declared is generally of no significance. In particular, textual order is not significant for the declaration and use of namespaces, constants, methods, properties, events, indexers, operators, instance constructors, finalizers, static constructors, and types. Declaration order is significant in the following ways:

  • Declaration order for field declarations determines the order in which their initializers (if any) are executed (§15.5.6.2, §15.5.6.3).
  • Local variables shall be defined before they are used (§7.6).
  • Declaration order for enum member declarations (§20.4) is significant when constant_expression values are omitted.

Example: The declaration space of a namespace is “open ended”, and two namespace declarations with the same fully qualified name contribute to the same declaration space. For example

namespace Megacorp.Data
{
    class Customer
    {
        ...
    }
}

namespace Megacorp.Data
{
    class Order
    {
        ...
    }
}

The two namespace declarations above contribute to the same declaration space, in this case declaring two classes with the fully qualified names Megacorp.Data.Customer and Megacorp.Data.Order. Because the two declarations contribute to the same declaration space, it would have caused a compile-time error if each contained a declaration of a class with the same name.

end example

Note: As specified above, the declaration space of a block includes any nested blocks. Thus, in the following example, the F and G methods result in a compile-time error because the name i is declared in the outer block and cannot be redeclared in the inner block. However, the H and I methods are valid since the two i’s are declared in separate non-nested blocks.

class A
{
    void F()
    {
        int i = 0;
        if (true)
        {
            int i = 1;
        }
    }

    void G()
    {
        if (true)
        {
            int i = 0;
        }
        int i = 1;
    }

    void H()
    {
        if (true)
        {
            int i = 0;
        }
        if (true)
        {
            int i = 1;
        }
    }

    void I()
    {
        for (int i = 0; i < 10; i++)
        {
            H();
        }
        for (int i = 0; i < 10; i++)
        {
            H();
        }
    }
}

end note

7.3 Members

7.3.1 General

Namespaces and types have members.

Note: The members of an entity are generally available through the use of a qualified name that starts with a reference to the entity, followed by a “.” token, followed by the name of the member. end note

Members of a type are either declared in the type declaration or inherited from the base class of the type. When a type inherits from a base class, all members of the base class, except instance constructors, finalizers, and static constructors become members of the derived type. The declared accessibility of a base class member does not control whether the member is inherited—inheritance extends to any member that is not an instance constructor, static constructor, or finalizer.

Note: However, an inherited member might not be accessible in a derived type, for example because of its declared accessibility (§7.4.2). end note

7.3.2 Namespace members

Namespaces and types that have no enclosing namespace are members of the global namespace. This corresponds directly to the names declared in the global declaration space.

Namespaces and types declared within a namespace are members of that namespace. This corresponds directly to the names declared in the declaration space of the namespace.

Namespaces have no access restrictions. It is not possible to declare private, protected, or internal namespaces, and namespace names are always publicly accessible.

7.3.3 Struct members

The members of a struct are the members declared in the struct and the members inherited from the struct’s direct base class System.ValueType and the indirect base class object.

The members of a simple type correspond directly to the members of the struct type aliased by the simple type (§8.3.5).

7.3.4 Enumeration members

The members of an enumeration are the constants declared in the enumeration and the members inherited from the enumeration’s direct base class System.Enum and the indirect base classes System.ValueType and object.

7.3.5 Class members

The members of a class are the members declared in the class and the members inherited from the base class (except for class object which has no base class). The members inherited from the base class include the constants, fields, methods, properties, events, indexers, operators, and types of the base class, but not the instance constructors, finalizers, and static constructors of the base class. Base class members are inherited without regard to their accessibility.

A class declaration may contain declarations of constants, fields, methods, properties, events, indexers, operators, instance constructors, finalizers, static constructors, and types.

The members of object (§8.2.3) and string (§8.2.5) correspond directly to the members of the class types they alias.

7.3.6 Interface members

The members of an interface are the members declared in the interface and in all base interfaces of the interface.

Note: The members in class object are not, strictly speaking, members of any interface (§19.4). However, the members in class object are available via member lookup in any interface type (§12.5). end note

7.3.7 Array members

The members of an array are the members inherited from class System.Array.

7.3.8 Delegate members

A delegate inherits members from class System.Delegate. Additionally, it contains a method named Invoke with the same return type and parameter list specified in its declaration (§21.2). An invocation of this method shall behave identically to a delegate invocation (§21.6) on the same delegate instance.

An implementation may provide additional members, either through inheritance or directly in the delegate itself.

7.4 Member access

7.4.1 General

Declarations of members allow control over member access. The accessibility of a member is established by the declared accessibility (§7.4.2) of the member combined with the accessibility of the immediately containing type, if any.

When access to a particular member is allowed, the member is said to be accessible. Conversely, when access to a particular member is disallowed, the member is said to be inaccessible. Access to a member is permitted when the textual location in which the access takes place is included in the accessibility domain (§7.4.3) of the member.

7.4.2 Declared accessibility

The declared accessibility of a member can be one of the following:

  • Public, which is selected by including a public modifier in the member declaration. The intuitive meaning of public is “access not limited”.
  • Protected, which is selected by including a protected modifier in the member declaration. The intuitive meaning of protected is “access limited to the containing class or interface, or classes or interfaces derived from the containing type”.
  • Internal, which is selected by including an internal modifier in the member declaration. The intuitive meaning of internal is “access limited to this assembly”.
  • Protected internal, which is selected by including both a protected and an internal modifier in the member declaration. The intuitive meaning of protected internal is “accessible within this assembly as well as types derived from the containing class”.
  • Private protected, which is selected by including both a private and a protected modifier in the member declaration. The intuitive meaning of private protected is “accessible within this assembly by the containing class and types derived from the containing class.”
  • Private, which is selected by including a private modifier in the member declaration. The intuitive meaning of private is “access limited to the containing type”.

Depending on the context in which a member declaration takes place, only certain types of declared accessibility are permitted. Furthermore, when a member declaration does not include any access modifiers, the context in which the declaration takes place determines the default declared accessibility.

  • Namespaces implicitly have public declared accessibility. No access modifiers are allowed on namespace declarations.
  • Types declared directly in compilation units or namespaces (as opposed to within other types) can have public or internal declared accessibility and default to internal declared accessibility.
  • Class members can have any of the permitted kinds of declared accessibility and default to private declared accessibility.

    Note: A type declared as a member of a class can have any of the permitted kinds of declared accessibility, whereas a type declared as a member of a namespace can have only public or internal declared accessibility. end note

  • Struct members can have public, internal, or private declared accessibility and default to private declared accessibility because structs are implicitly sealed. Struct members introduced in a struct (that is, not inherited by that struct) cannot have protected, protected internal, or private protected declared accessibility.

    Note: A type declared as a member of a struct can have public, internal, or private declared accessibility, whereas a type declared as a member of a namespace can have only public or internal declared accessibility. end note

  • Interface members implicitly have public declared accessibility.
  • Enumeration members implicitly have public declared accessibility. No access modifiers are allowed on enumeration member declarations.

7.4.3 Accessibility domains

The accessibility domain of a member consists of the (possibly disjoint) sections of program text in which access to the member is permitted. For purposes of defining the accessibility domain of a member, a member is said to be top-level if it is not declared within a type, and a member is said to be nested if it is declared within another type. Furthermore, the program text of a program is defined as all text contained in all compilation units of the program, and the program text of a type is defined as all text contained in the type_declarations of that type (including, possibly, types that are nested within the type).

The accessibility domain of a predefined type (such as object, int, or double) is unlimited.

The accessibility domain of a top-level unbound type T (§8.4.4) that is declared in a program P is defined as follows:

  • If the declared accessibility of T is public, the accessibility domain of T is the program text of P and any program that references P.
  • If the declared accessibility of T is internal, the accessibility domain of T is the program text of P.

Note: From these definitions, it follows that the accessibility domain of a top-level unbound type is always at least the program text of the program in which that type is declared. end note

The accessibility domain for a constructed type T<A₁, ..., Aₑ> is the intersection of the accessibility domain of the unbound generic type T and the accessibility domains of the type arguments A₁, ..., Aₑ.

The accessibility domain of a nested member M declared in a type T within a program P, is defined as follows (noting that M itself might possibly be a type):

  • If the declared accessibility of M is public, the accessibility domain of M is the accessibility domain of T.
  • If the declared accessibility of M is protected internal, let D be the union of the program text of P and the program text of any type derived from T, which is declared outside P. The accessibility domain of M is the intersection of the accessibility domain of T with D.
  • If the declared accessibility of M is private protected, let D be the intersection of the program text of P and the program text of T and any type derived from T. The accessibility domain of M is the intersection of the accessibility domain of T with D.
  • If the declared accessibility of M is protected, let D be the union of the program text of Tand the program text of any type derived from T. The accessibility domain of M is the intersection of the accessibility domain of T with D.
  • If the declared accessibility of M is internal, the accessibility domain of M is the intersection of the accessibility domain of T with the program text of P.
  • If the declared accessibility of M is private, the accessibility domain of M is the program text of T.

Note: From these definitions it follows that the accessibility domain of a nested member is always at least the program text of the type in which the member is declared. Furthermore, it follows that the accessibility domain of a member is never more inclusive than the accessibility domain of the type in which the member is declared. end note

Note: In intuitive terms, when a type or member M is accessed, the following steps are evaluated to ensure that the access is permitted:

  • First, if M is declared within a type (as opposed to a compilation unit or a namespace), a compile-time error occurs if that type is not accessible.
  • Then, if M is public, the access is permitted.
  • Otherwise, if M is protected internal, the access is permitted if it occurs within the program in which M is declared, or if it occurs within a class derived from the class in which M is declared and takes place through the derived class type (§7.4.4).
  • Otherwise, if M is protected, the access is permitted if it occurs within the class in which M is declared, or if it occurs within a class derived from the class in which M is declared and takes place through the derived class type (§7.4.4).
  • Otherwise, if M is internal, the access is permitted if it occurs within the program in which M is declared.
  • Otherwise, if M is private, the access is permitted if it occurs within the type in which M is declared.
  • Otherwise, the type or member is inaccessible, and a compile-time error occurs. end note

Example: In the following code

public class A
{
    public static int X;
    internal static int Y;
    private static int Z;
}

internal class B
{
    public static int X;
    internal static int Y;
    private static int Z;

    public class C
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }

    private class D
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }
}

the classes and members have the following accessibility domains:

  • The accessibility domain of A and A.X is unlimited.
  • The accessibility domain of A.Y, B, B.X, B.Y, B.C, B.C.X, and B.C.Y is the program text of the containing program.
  • The accessibility domain of A.Z is the program text of A.
  • The accessibility domain of B.Z and B.D is the program text of B, including the program text of B.C and B.D.
  • The accessibility domain of B.C.Z is the program text of B.C.
  • The accessibility domain of B.D.X and B.D.Y is the program text of B, including the program text of B.C and B.D.
  • The accessibility domain of B.D.Z is the program text of B.D. As the example illustrates, the accessibility domain of a member is never larger than that of a containing type. For example, even though all X members have public declared accessibility, all but A.X have accessibility domains that are constrained by a containing type.

end example

As described in §7.3, all members of a base class, except for instance constructors, finalizers, and static constructors, are inherited by derived types. This includes even private members of a base class. However, the accessibility domain of a private member includes only the program text of the type in which the member is declared.

Example: In the following code

class A
{
    int x;

    static void F(B b)
    {
        b.x = 1;         // Ok
    }
}

class B : A
{
    static void F(B b)
    {
        b.x = 1;         // Error, x not accessible
    }
}

the B class inherits the private member x from the A class. Because the member is private, it is only accessible within the class_body of A. Thus, the access to b.x succeeds in the A.F method, but fails in the B.F method.

end example

7.4.4 Protected access

When a protected or private protected instance member is accessed outside the program text of the class in which it is declared, and when a protected internal instance member is accessed outside the program text of the program in which it is declared, the access shall take place within a class declaration that derives from the class in which it is declared. Furthermore, the access is required to take place through an instance of that derived class type or a class type constructed from it. This restriction prevents one derived class from accessing protected members of other derived classes, even when the members are inherited from the same base class. Instance interface members defined with protected or private protected access cannot be accessed from a class or struct that implements that interface; these can be accessed only from derived interfaces. However, class and struct types can implement protected instance members declared in an interface they implement.

Let B be a base class that declares a protected instance member M, and let D be a class that derives from B. Within the class_body (or record_class_body) of D, access to M can take one of the following forms:

  • An unqualified type_name or primary_expression of the form M.
  • A primary_expression of the form E.M, provided the type of E is T or a class derived from T, where T is the class D, or a class type constructed from D.
  • A primary_expression of the form base.M.
  • A primary_expression of the form base[argument_list].

In addition to these forms of access, a derived class can access a protected instance constructor of a base class in a constructor_initializer (§15.11.2).

Example: In the following code

public class A
{
    protected int x;

    static void F(A a, B b)
    {
        a.x = 1; // Ok
        b.x = 1; // Ok
    }
}

public class B : A
{
    static void F(A a, B b)
    {
        a.x = 1; // Error, must access through instance of B
        b.x = 1; // Ok
    }
}

within A, it is possible to access x through instances of both A and B, since in either case the access takes place through an instance of A or a class derived from A. However, within B, it is not possible to access x through an instance of A, since A does not derive from B.

end example

Example:

class C<T>
{
    protected T x;
}

class D<T> : C<T>
{
    static void F()
    {
        D<T> dt = new D<T>();
        D<int> di = new D<int>();
        D<string> ds = new D<string>();
        dt.x = default(T);
        di.x = 123;
        ds.x = "test";
    }
}

Here, the three assignments to x are permitted because they all take place through instances of class types constructed from the generic type.

end example

Note: The accessibility domain (§7.4.3) of a protected member declared in a generic class includes the program text of all class declarations derived from any type constructed from that generic class. In the example:

class C<T>
{
    protected static T x;
}

class D : C<string>
{
    static void Main()
    {
        C<int>.x = 5;
    }
}

the reference to protected member C<int>.x in D is valid even though the class D derives from C<string>. end note

7.4.5 Accessibility constraints

Several constructs in the C# language require a type to be at least as accessible as a member or another type. A type T is said to be at least as accessible as a member or type M if the accessibility domain of T is a superset of the accessibility domain of M. In other words, T is at least as accessible as M if T is accessible in all contexts in which M is accessible.

The following accessibility constraints exist:

  • The direct base class of a class type shall be at least as accessible as the class type itself.
  • The explicit base interfaces of an interface type shall be at least as accessible as the interface type itself.
  • The return type and parameter types of a delegate type shall be at least as accessible as the delegate type itself.
  • The type of a constant shall be at least as accessible as the constant itself.
  • The type of a field shall be at least as accessible as the field itself.
  • The return type and parameter types of a method shall be at least as accessible as the method itself.
  • The type of a property shall be at least as accessible as the property itself.
  • The type of an event shall be at least as accessible as the event itself.
  • The type and parameter types of an indexer shall be at least as accessible as the indexer itself.
  • The return type and parameter types of an operator shall be at least as accessible as the operator itself.
  • The parameter types of an instance constructor shall be at least as accessible as the instance constructor itself.
  • An interface or class type constraint on a type parameter shall be at least as accessible as the member which declares the constraint.

Example: In the following code

class A {...}
public class B: A {...}

the B class results in a compile-time error because A is not at least as accessible as B.

end example

Example: Likewise, in the following code

class A {...}

public class B
{
    A F() {...}
    internal A G() {...}
    public A H() {...}
}

the H method in B results in a compile-time error because the return type A is not at least as accessible as the method.

end example

7.5 Signatures and overloading

Methods, instance constructors, indexers, and operators are characterized by their signatures:

  • The signature of a method consists of the name of the method, the number of type parameters, and the type and parameter-passing mode of each of its parameters, considered in the order left to right. For these purposes, any type parameter of the method that occurs in the type of a parameter is identified not by its name, but by its ordinal position in the type parameter list of the method. The signature of a method specifically does not include the return type, parameter names, type parameter names, type parameter constraints, the params, scoped, or this parameter modifiers, nor whether parameters are required or optional.
  • The signature of an instance constructor consists of the type and parameter-passing mode of each of its parameters, considered in the order left to right. The signature of an instance constructor specifically does not include the params modifier that may be specified for the right-most parameter, nor whether parameters are required or optional.
  • The signature of an indexer consists of the type of each of its parameters, considered in the order left to right. The signature of an indexer specifically does not include the element type, or the scoped modifier, nor does it include the params modifier that may be specified for the right-most parameter, or the scoped modifier, nor whether parameters are required or optional.
  • The signature of an operator consists of the name of the operator and the type of each of its parameters, considered in the order left to right. The signature of an operator specifically does not include the result type or the scoped modifier.
  • The signature of a conversion operator consists of the source type and the target type. The implicit or explicit classification of a conversion operator is not part of the signature nor is the scoped modifier.
  • Two signatures of the same member kind (method, instance constructor, indexer or operator) are considered to be the same signatures if they have the same name, number of type parameters, number of parameters, and parameter-passing modes, and an identity conversion exists between the types of their corresponding parameters (§10.2.2).

Signatures are the enabling mechanism for overloading of members in classes, structs, and interfaces:

  • Overloading of methods permits a class, struct, or interface to declare multiple methods with the same name, provided their signatures are unique within that class, struct, or interface.
  • Overloading of instance constructors permits a class or struct to declare multiple instance constructors, provided their signatures are unique within that class or struct.
  • Overloading of indexers permits a class, struct, or interface to declare multiple indexers, provided their signatures are unique within that class, struct, or interface.
  • Overloading of operators permits a class or struct to declare multiple operators with the same name, provided their signatures are unique within that class or struct.

Although parameter_mode_modifiers are considered part of a signature, members declared in a single type cannot differ in signature solely by those modifiers. A compile-time error occurs if two members are declared in the same type with signatures that would be the same if all parameters in both methods with out or in modifiers were changed to ref modifiers. For other purposes of signature matching (e.g., hiding or overriding), parameter_mode_modifiers are considered part of the signature and do not match each other.

Note: This restriction is to allow C# programs to be easily translated to run on a platform that does not provide a way to define methods that differ solely in their parameter_mode_modifiers. end note

The types object and dynamic are not distinguished when comparing signatures. Therefore members declared in a single type whose signatures differ only by replacing object with dynamic are not allowed.

Example: The following example shows a set of overloaded method declarations along with their signatures.

interface ITest
{
    void F();                   // F()
    void F(int x);              // F(int)
    void F(ref int x);          // F(ref int)
    void F(ref readonly int x); // F(ref int) error
    void F(out int x);          // F(out int) error
    void F(object o);           // F(object)
    void F(dynamic d);          // error.
    void F(int x, int y);       // F(int, int)
    int F(string s);            // F(string)
    int F(int x);               // F(int) error
    void F(string[] a);         // F(string[])
    void F(params string[] a);  // F(string[]) error
    void F<S>(S s);             // F<0>(0)
    void F<T>(T t);             // F<0>(0) error
    void F<S,T>(S s);           // F<0,1>(0)
    void F<T,S>(S s);           // F<0,1>(1) ok
}

Note that any parameter_mode_modifiers (§15.6.2) are part of a signature. Thus, F(int), F(in int), F(out int) , F(ref int), and F(ref readonly int) are all unique signatures. However, F(in int), F(out int), F(ref int), and F(ref readonly int) cannot be declared within the same interface because their signatures differ solely by their parameter_mode_modifiers. Also, note that the return type and the params modifier are not part of a signature, so it is not possible to overload solely based on return type or on the inclusion or exclusion of the params modifier. As such, the declarations of the methods F(int) and F(params string[]) identified above, result in a compile-time error. end example

7.6 Scopes

7.6.1 General

The scope of a name is the region of program text within which it is possible to refer to the entity declared by the name without qualification of the name. Scopes can be nested, and an inner scope may redeclare the meaning of a name from an outer scope. (This does not, however, remove the restriction imposed by §7.2 that within a nested block it is not possible to declare a local variable or local constant with the same name as a local variable or local constant in an enclosing block.) The name from the outer scope is then said to be hidden in the region of program text covered by the inner scope, and access to the outer name is only possible by qualifying the name.

Note: It may not be possible to access the hidden outer name from the inner scope because there is no way of qualifying it, such as with type parameters on nested type declarations. end note

  • The scope of a namespace member declared by a namespace_member_declaration (§14.7) with no enclosing namespace_declaration is the entire program text.
  • The scope of a namespace member declared by a compilation_unit_body whose fully qualified name is N, is every compilation_unit_body whose fully qualified name is N or starts with N, followed by a period.
  • The scope of a namespace member declared by a namespace_member_declaration within a namespace_declaration whose fully qualified name is N, is the namespace_body of every namespace_declaration whose fully qualified name is N or starts with N, followed by a period. Per §14.3, file_scoped_namespace_declarations are treated as equivalent namespace_declarations for this rule.
  • The scope of a name defined by an extern_alias_directive (§14.4) extends over the global_using_directives, using_directives, global_attributes, and compilation_unit_body of its immediately containing compilation_unit or namespace_body. An extern_alias_directive does not contribute any new members to the underlying declaration space. In other words, an extern_alias_directive is not transitive, but, rather, affects only the compilation_unit, namespace_body, and file_scoped_namespace_declaration in which it occurs.
  • The scope of a name defined or imported by a global_using_directive extends over the global_attributes and compilation_unit_body of all the compilation_units in the program.
  • The scope of a name defined or imported by a using_directive (§14.6) extends over the global_attributes, statement_lists, and namespace_member_declarations of the compilation_unit or namespace_body in which the using_directive occurs. A using_directive may make zero or more namespace or type names available within a particular compilation_unit or namespace_body, but does not contribute any new members to the underlying declaration space. In other words, a using_directive is not transitive but rather affects only the compilation_unit or namespace_body in which it occurs.
  • The scope of a type parameter declared by a type_parameter_list on a class_declaration (§15.2) is the class_base, type_parameter_constraints_clauses, and class_body (or record_class_body) of that class_declaration.

    Note: Unlike members of a class, this scope does not extend to derived classes. end note

  • The scope of a type parameter declared by a type_parameter_list on a struct_declaration (§16.2) is the struct_interfaces, type_parameter_constraints_clauses, and struct_body (or record_struct_body) of that struct_declaration.
  • The scope of a type parameter declared by a type_parameter_list on an interface_declaration (§19.2) is the interface_base, type_parameter_constraints_clauses, and interface_body of that interface_declaration.
  • The scope of a type parameter declared by a type_parameter_list on a delegate_declaration (§21.2) is the return_type, parameter_list, and type_parameter_constraints_clauses of that delegate_declaration.
  • The scope of a type parameter declared by a type_parameter_list on a method_declaration (§15.6.1) is the method_declaration and nameof expressions in an attribute on the method_declaration or its parameters.
  • The scope of a member declared by a class_member_declaration (§15.3.1) is the class_body (or record_class_body) in which the declaration occurs. In addition, the scope of a class member extends to the class_body (or record_class_body) of those derived classes that are included in the accessibility domain (§7.4.3) of the member.
  • The scope of a member declared by a struct_member_declaration (§16.3) is the struct_body (or record_struct_body) in which the declaration occurs.
  • The scope of a member declared by an enum_member_declaration (§20.4) is the enum_body in which the declaration occurs.
  • The scope of a parameter declared in a method_declaration (§15.6) is the method_body or ref_method_body of that method_declaration and nameof expressions in an attribute on the method declaration or its parameters.
  • The scope of a parameter declared in an indexer_declaration (§15.9) is the indexer_body of that indexer_declaration.
  • The scope of a parameter declared in an operator_declaration (§15.10) is the operator_body of that operator_declaration.
  • The scope of a parameter declared in a constructor_declaration (§15.11) is the constructor_initializer and block of that constructor_declaration.
  • With the exception of discard parameters (§12.22.2), the scope of a parameter declared in a lambda_expression (§12.22) is the lambda_expression_body of that lambda_expression and nameof expressions in an attribute on the anonymous function or its parameters.
  • With the exception of discard parameters (§12.22.2), the scope of a parameter declared in an anonymous_method_expression (§12.22) is the block of that anonymous_method_expression and nameof expressions in an attribute on the anonymous function or its parameters.
  • The scope of a label declared in a labeled_statement (§13.5) is the block in which the declaration occurs.
  • The scope of a local variable declared in a local_variable_declaration (§13.6.2) is the block in which the declaration occurs.
  • The scope of a local variable declared in a switch_block of a switch statement (§13.8.3) is the switch_block.
  • The scope of a local variable declared in a for_initializer of a for statement (§13.9.4) is the for_initializer, for_condition, for_iterator, and embedded_statement of the for statement.
  • The scope of a local constant declared in a local_constant_declaration (§13.6.3) is the block in which the declaration occurs. It is a compile-time error to refer to a local constant in a textual position that precedes its constant_declarator.
  • The scope of a variable declared as part of a foreach_statement, using_statement, lock_statement or query_expression is determined by the expansion of the given construct.

Within the scope of a namespace, class, struct, or enumeration member it is possible to refer to the member in a textual position that precedes the declaration of the member.

Example:

class A
{
    void F()
    {
        i = 1;
    }

    int i = 0;
}

Here, it is valid for F to refer to i before it is declared.

end example

Within the scope of a local variable, it is a compile-time error to refer to the local variable in a textual position that precedes its declarator.

Example:

class A
{
    int i = 0;

    void F()
    {
        i = 1;                // Error, use precedes declaration
        int i;
        i = 2;
    }

    void G()
    {
        int j = (j = 1);     // Valid
    }

    void H()
    {
        int a = 1, b = ++a; // Valid
    }
}

In the F method above, the first assignment to i specifically does not refer to the field declared in the outer scope. Rather, it refers to the local variable and it results in a compile-time error because it textually precedes the declaration of the variable. In the G method, the use of j in the initializer for the declaration of j is valid because the use does not precede the declarator. In the H method, a subsequent declarator correctly refers to a local variable declared in an earlier declarator within the same local_variable_declaration.

end example

Note: The scoping rules for local variables and local constants are designed to guarantee that the meaning of a name used in an expression context is always the same within a block. If the scope of a local variable were to extend only from its declaration to the end of the block, then in the example above, the first assignment would assign to the instance variable and the second assignment would assign to the local variable, possibly leading to compile-time errors if the statements of the block were later to be rearranged.)

The meaning of a name within a block may differ based on the context in which the name is used. In the example

class A {}

class Test
{
    static void Main()
    {
        string A = "hello, world";
        string s = A;                      // expression context
        Type t = typeof(A);                // type context
        Console.WriteLine(s);              // writes "hello, world"
        Console.WriteLine(t);              // writes "A"
    }
}

the name A is used in an expression context to refer to the local variable A and in a type context to refer to the class A.

end note

As described in §7.1.3, top-level source tokens are enclosed by the generated entry-point method.

For the purpose of simple-name evaluation, once the global namespace is reached, first, an attempt is made to evaluate the name within the generated entry-point method and only if this attempt fails is the evaluation within the global namespace declaration performed.

This could lead to name shadowing of namespaces and types declared within the global namespace as well as to shadowing of imported names.

If the simple name evaluation occurs outside of the top-level statements and the evaluation yields a top-level local variable or function, a compile-time error results.

7.6.2 Name hiding

7.6.2.1 General

The scope of an entity typically encompasses more program text than the declaration space of the entity. In particular, the scope of an entity may include declarations that introduce new declaration spaces containing entities of the same name. Such declarations cause the original entity to become hidden. Conversely, an entity is said to be visible when it is not hidden.

Name hiding occurs when scopes overlap through nesting and when scopes overlap through inheritance. The characteristics of the two types of hiding are described in the following subclauses.

7.6.2.2 Hiding through nesting

Name hiding through nesting can occur as a result of nesting namespaces or types within namespaces, as a result of nesting types within classes or structs, as a result of a local function or a lambda, and as a result of parameter, local variable, and local constant declarations.

Example: In the following code

class A
{
    int i = 0;
    void F()
    {
        int i = 1;

        void M1()
        {
            float i = 1.0f;
            Func<double, double> doubler = (double i) => i * 2.0;
        }
    }

    void G()
    {
        i = 1;
    }
}

within the F method, the instance variable i is hidden by the local variable i, but within the G method, i still refers to the instance variable. Inside the local function M1 the float i hides the immediate-outer i. The lambda parameter i hides the float i inside the lambda body.

end example

When a name in an inner scope hides a name in an outer scope, it hides all overloaded occurrences of that name.

Example: In the following code

class Outer
{
    static void F(int i) {}
    static void F(string s) {}

    class Inner
    {
        static void F(long l) {}

        void G()
        {
            F(1); // Invokes Outer.Inner.F
            F("Hello"); // Error
        }
    }
}

the call F(1) invokes the F declared in Inner because all outer occurrences of F are hidden by the inner declaration. For the same reason, the call F("Hello") results in a compile-time error.

end example

7.6.2.3 Hiding through inheritance

Name hiding through inheritance occurs when classes or structs redeclare names that were inherited from base classes. This type of name hiding takes one of the following forms:

  • A constant, field, property, event, or type introduced in a class, struct, or interface hides all base class members with the same name.
  • A method introduced in a class, struct, or interface hides all non-method base class members with the same name, and all base class methods with the same signature (§7.5).
  • An indexer introduced in a class, struct, or interface hides all base type indexers with the same signature (§7.5) .

The rules governing operator declarations (§15.10) make it impossible for a derived class to declare an operator with the same signature as an operator in a base class. Thus, operators never hide one another.

Contrary to hiding a name from an outer scope, hiding a visible name from an inherited scope causes a warning to be reported.

Example: In the following code

class Base
{
    public void F() {}
}

class Derived : Base
{
    public void F() {} // Warning, hiding an inherited name
}

the declaration of F in Derived causes a warning to be reported. Hiding an inherited name is specifically not an error, since that would preclude separate evolution of base classes. For example, the above situation might have come about because a later version of Base introduced an F method that was not present in an earlier version of the class.

end example

The warning caused by hiding an inherited name can be eliminated through use of the new modifier:

Example:

class Base
{
    public void F() {}
}

class Derived : Base
{
    public new void F() {}
}

The new modifier indicates that the F in Derived is “new”, and that it is indeed intended to hide the inherited member.

end example

A declaration of a new member hides an inherited member only within the scope of the new member.

Example:

class Base
{
    public static void F() {}
}

class Derived : Base
{
    private new static void F() {} // Hides Base.F in Derived only
}

class MoreDerived : Derived
{
    static void G()
    {
        F();                       // Invokes Base.F
    }
}

In the example above, the declaration of F in Derived hides the F that was inherited from Base, but since the new F in Derived has private access, its scope does not extend to MoreDerived. Thus, the call F() in MoreDerived.G is valid and will invoke Base.F.

end example

7.7 Namespace and type names

7.7.1 General

Several contexts in a C# program require a namespace_name or a type_name to be specified.

namespace_name
    : namespace_or_type_name
    ;

type_name
    : namespace_or_type_name
    ;

namespace_or_type_name
    : identifier type_argument_list? ('.' identifier type_argument_list?)*
    | qualified_alias_member ('.' identifier type_argument_list?)*
    ;

A namespace_name is a namespace_or_type_name that refers to a namespace.

Following resolution as described below, the namespace_or_type_name of a namespace_name shall refer to a namespace, or otherwise a compile-time error occurs. No type arguments (§8.4.2) can be present in a namespace_name (only types can have type arguments).

A type_name is a namespace_or_type_name that refers to a type.

Following resolution as described below, the namespace_or_type_name of a type_name shall refer to a type, or otherwise a compile-time error occurs.

A namespace_or_type_name refers to a type or a namespace. Resolution to a particular namespace or type involves two steps based on dividing the grammar into a leading part, being one of the grammar fragments:

  • identifier type_argument_list?
  • qualified_alias_member

and a trailing part, being the grammar fragment:

  • ('.' identifier type_argument_list?)*

First the leading part is resolved to determine R₀, the starting namespace or type.

If the leading part of the namespace_or_type_name is a qualified_alias_member, then R₀ is the namespace or type identified by resolving that as described in §14.9.1.

Otherwise, the leading part, being the grammar fragment identifier type_argument_list?, will have one of the forms:

  • I
  • I<A₁, ..., Aₓ>

where:

  • I is a single identifier; and
  • <A₁, ..., Aₓ> is a type_argument_list, when no type_argument_list is specified consider x to be zero.

R₀ is determined as follows:

  • If x is zero and the namespace_or_type_name appears within a generic method declaration (§15.6) but outside the attributes of its method-header, and if that declaration includes a type parameter (§15.2.3) with name I, then R₀ refers to that type parameter.
  • Otherwise, if the namespace_or_type_name appears within a type declaration, then for each instance type T (§15.3.2), starting with the instance type of that type declaration and continuing with the instance type of each enclosing class, struct, or interface declaration (if any):
    • If x is zero and the declaration of T includes a type parameter with name I, then R₀ refers to that type parameter.
    • Otherwise, if the namespace_or_type_name appears within the body of the type declaration, and T or any of its base types contain a nested accessible type having name I and x type parameters, then R₀ refers to that type constructed with the given type arguments. If there is more than one such type, the type declared within the more derived type is selected. It is a compiler error if no type is more derived than all others.

      Note: Non-type members (constants, fields, methods, properties, indexers, operators, instance constructors, finalizers, and static constructors) and type members with a different number of type parameters are ignored when determining the meaning of the namespace_or_type_name. end note

    • Otherwise, for each namespace N, starting with the namespace in which the namespace_or_type_name occurs, continuing with each enclosing namespace (if any), and ending with the global namespace, the following steps are evaluated until an entity is located:
      • If x is zero and I is the name of a namespace in N, then:
        • If the location where the namespace_or_type_name occurs is enclosed by a namespace declaration for N and the namespace declaration contains an extern_alias_directive or using_alias_directive that associates the name I with a namespace or type, or any namespace declaration for N in the program contains a global_using_alias_directive that associates the name I with a namespace or type, then the namespace_or_type_name is ambiguous and a compile-time error occurs.
        • Otherwise, R₀ refers to the namespace named I in N.
      • Otherwise, if N contains an accessible type having name I and x type parameters, then:
        • If x is zero and the location where the namespace_or_type_name occurs is enclosed by a namespace declaration for N and the namespace declaration contains an extern_alias_directive or using_alias_directive that associates the name I with a namespace or type, or any namespace declaration for N in the program contains a global_using_alias_directive that associates the name I with a namespace or type, then the namespace_or_type_name is ambiguous and a compile-time error occurs.
        • Otherwise, R₀ refers to the type constructed with the given type arguments.
      • Otherwise, if the location where the namespace_or_type_name occurs is enclosed by a namespace declaration for N:
        • If x is zero and the namespace declaration contains an extern_alias_directive or using_alias_directive that associates the name I with an imported namespace or type, or any namespace declaration for N in the program contains a global_using_alias_directive that associates the name I with an imported namespace or type, then R₀ refers to that namespace or type.
        • Otherwise, if the namespaces imported by the using_namespace_directives of the namespace declaration and the namespaces and type declarations imported by the global_using_namespace_directives and global_using_static_directives of any namespace declaration for N in the program contain exactly one type having name I and x type parameters, then R₀ refers to that type constructed with the given type arguments.
        • Otherwise, if the namespaces imported by the using_namespace_directives of the namespace declaration and the namespaces and type declarations imported by the global_using_namespace_directives and global_using_static_directives of any namespace declaration for N in the program contain more than one type having name I and x type parameters, then the namespace_or_type_name is ambiguous and a compile-time error occurs.
    • Otherwise, the namespace_or_type_name is undefined and a compile-time error occurs.

If R₀ has been resolved successfully the trailing part of the namespace_or_type_name is resolved. The trailing grammar fragment consists of k ≥ 0 repetitions, with each repetition further resolving the referenced namespace or type.

If k is zero, i.e. there is no trailing part, then the namespace_or_type_name resolves to R₀.

Otherwise each repetition will have one of the forms:

  • .I
  • .I<A₁, ..., Aₓ>

where I, A and x are defined as above.

For each repetition n, where 1 ≤ n ≤ k, its resolution, Rₙ; which involves Rₚ, where p = n - 1, the resolution of the preceding repetition; is determined as follows:

  • If x is zero and Rₚ refers to a namespace and Rₚ contains a nested namespace with name I, then Rₙ refers to that nested namespace.
  • Otherwise, if Rₚ refers to a namespace and Rₚ contains an accessible type having name I and x type parameters, then Rₙ refers to that type constructed with the given type arguments.
  • Otherwise, if Rₚ refers to a (possibly constructed) class, struct, or interface type and Rₚ or any of its base types contain a nested accessible type having name I and x type parameters, then Rₙ refers to that type constructed with the given type arguments. If there is more than one such type, the type declared within the more derived type is selected. It is a compiler error if no type is more derived than all others.

    Note: If the meaning of T.I, for some type T, is being determined as part of resolving the base class specification of T then the direct base class of T is considered to be object (§15.2.4.2). end note

  • Otherwise, the namespace_or_type_name is invalid and a compile-time error occurs.

The resolution of the namespace_or_type_name is the resolution of the final repetition, Rₖ.

A namespace_or_type_name is permitted to reference a static class (§15.2.2.4) only if

  • The namespace_or_type_name is the T in a namespace_or_type_name of the form T.I, or
  • The namespace_or_type_name is the T in a typeof_expression (§12.8.18) of the form typeof(T)

Example:

interface A
{
    class NestedClass { public static void M() {} }
}

interface B
{
    class NestedClass { public static void M() {} }
}

interface C : A, B
{
    public void Test() { NestedClass.M(); } // ambiguity between
                                            // A.NestedClass and B.NestedClass
}

In the example above, the call to NestedClass.M() in C.Test() is ambiguous between B.NestedClass.M() and A.NestedClass.M() because neither is more derived than the other. An explicit reference to either A.NestedClass.M() or B.NestedClass.M() is required to resolve the ambiguity.

end example

7.7.2 Unqualified names

Every namespace declaration and type declaration has an unqualified name determined as follows:

  • For a namespace declaration, the unqualified name is the qualified_identifier specified in the declaration.
  • For a type declaration with no type_parameter_list, the unqualified name is the identifier specified in the declaration.
  • For a type declaration with K type parameters, the unqualified name is the identifier specified in the declaration, followed by the generic_dimension_specifier (§12.8.18) for K type parameters.

7.7.3 Fully qualified names

Every namespace and type declaration has a fully qualified name, which uniquely identifies the namespace or type declaration amongst all others within the program. The fully qualified name of a namespace or type declaration with unqualified name N is determined as follows:

  • If N is a member of the global namespace, its fully qualified name is N.
  • Otherwise, its fully qualified name is S.N, where S is the fully qualified name of the namespace or type declaration in which N is declared.

In other words, the fully qualified name of N is the complete hierarchical path of identifiers and generic_dimension_specifiers that lead to N, starting from the global namespace. Because every member of a namespace or type shall have a unique name, it follows that the fully qualified name of a namespace or type declaration is always unique. It is a compile-time error for the same fully qualified name to refer to two distinct entities. In particular:

  • It is an error for both a namespace declaration and a type declaration to have the same fully qualified name.
  • It is an error for two different kinds of type declarations to have the same fully qualified name (for example, if both a struct and class declaration have the same fully qualified name).
  • It is an error for a type declaration without the partial modifier to have the same fully qualified name as another type declaration (§15.2.7).

Example: The example below shows several namespace and type declarations along with their associated fully qualified names.

class A {}                 // A
namespace X                // X
{
    class B                // X.B
    {
        class C {}         // X.B.C
    }
    namespace Y            // X.Y
    {
        class D {}         // X.Y.D
    }
}
namespace X.Y              // X.Y
{
    class E {}             // X.Y.E
    class G<T>             // X.Y.G<>
    {
        class H {}         // X.Y.G<>.H
    }
    class G<S,T>           // X.Y.G<,>
    {
        class H<U> {}      // X.Y.G<,>.H<>
    }
}

end example

7.8 Automatic memory management

C# employs automatic memory management, which frees developers from manually allocating and freeing the memory occupied by objects. Automatic memory management policies are implemented by a garbage collector. The memory management life cycle of an object is as follows:

  1. When the object is created, memory is allocated for it, the constructor is run, and the object is considered live.
  2. If neither the object nor any of its instance fields can be accessed by any possible continuation of execution, other than the running of finalizers, the object is considered no longer in use and it becomes eligible for finalization.

    Note: The C# compiler and the garbage collector might choose to analyze code to determine which references to an object might be used in the future. For instance, if a local variable that is in scope is the only existing reference to an object, but that local variable is never referred to in any possible continuation of execution from the current execution point in the procedure, the garbage collector might (but is not required to) treat the object as no longer in use. end note

  3. Once the object is eligible for finalization, at some unspecified later time the finalizer (§15.13) (if any) for the object is run. Under normal circumstances the finalizer for the object is run once only, though implementation-defined APIs may allow this behavior to be overridden.
  4. Once the finalizer for an object is run, if neither the object nor any of its instance fields can be accessed by any possible continuation of execution, including the running of finalizers, the object is considered inaccessible and the object becomes eligible for collection.

    Note: An object which could previously not be accessed may become accessible again due to its finalizer. An example of this is provided below. end note

  5. Finally, at some time after the object becomes eligible for collection, the garbage collector frees the memory associated with that object.

The garbage collector maintains information about object usage, and uses this information to make memory management decisions, such as where in memory to locate a newly created object, when to relocate an object, and when an object is no longer in use or inaccessible.

Like other languages that assume the existence of a garbage collector, C# is designed so that the garbage collector might implement a wide range of memory management policies. C# specifies neither a time constraint within that span, nor an order in which finalizers are run. Whether or not finalizers are run as part of application termination is implementation-defined (§7.1).

The behavior of the garbage collector can be controlled, to some degree, via static methods on the class System.GC. This class can be used to request a collection to occur, finalizers to be run (or not run), and so forth.

Example: Since the garbage collector is allowed wide latitude in deciding when to collect objects and run finalizers, a conforming implementation might produce output that differs from that shown by the following code. The program

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }
}

class B
{
    object Ref;
    public B(object o)
    {
        Ref = o;
    }

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
    }
}

class Test
{
    static void Main()
    {
        B b = new B(new A());
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

creates an instance of class A and an instance of class B. These objects become eligible for garbage collection when the variable b is assigned the value null, since after this time it is impossible for any user-written code to access them. The output could be either

Finalize instance of A
Finalize instance of B

or

Finalize instance of B
Finalize instance of A

because the language imposes no constraints on the order in which objects are garbage collected.

In subtle cases, the distinction between “eligible for finalization” and “eligible for collection” can be important. For example,

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }

    public void F()
    {
        Console.WriteLine("A.F");
        Test.RefA = this;
    }
}

class B
{
    public A Ref;

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
        Ref.F();
    }
}

class Test
{
    public static A RefA;
    public static B RefB;

    static void Main()
    {
        RefB = new B();
        RefA = new A();
        RefB.Ref = RefA;
        RefB = null;
        RefA = null;
        // A and B now eligible for finalization
        GC.Collect();
        GC.WaitForPendingFinalizers();
        // B now eligible for collection, but A is not
        if (RefA != null)
        {
            Console.WriteLine("RefA is not null");
        }
    }
}

In the above program, if the garbage collector chooses to run the finalizer of A before the finalizer of B, then the output of this program might be:

Finalize instance of A
Finalize instance of B
A.F
RefA is not null

Note that although the instance of A was not in use and A’s finalizer was run, it is still possible for methods of A (in this case, F) to be called from another finalizer. Also, note that running of a finalizer might cause an object to become usable from the mainline program again. In this case, the running of B’s finalizer caused an instance of A that was previously not in use, to become accessible from the live reference Test.RefA. After the call to WaitForPendingFinalizers, the instance of B is eligible for collection, but the instance of A is not, because of the reference Test.RefA.

end example

7.9 Execution order

Execution of a C# program proceeds such that the side effects of each executing thread are preserved at critical execution points. A side effect is defined as a read or write of a volatile field, a write to a non-volatile variable, a write to an external resource, and the throwing of an exception. The critical execution points at which the order of these side effects shall be preserved are references to volatile fields (§15.5.4), lock statements (§13.13), and thread creation and termination. The execution environment is free to change the order of execution of a C# program, subject to the following constraints:

  • Data dependence is preserved within a thread of execution. That is, the value of each variable is computed as if all statements in the thread were executed in original program order.
  • Initialization ordering rules are preserved (§15.5.5, §15.5.6).
  • The ordering of side effects is preserved with respect to volatile reads and writes (§15.5.4). Additionally, the execution environment need not evaluate part of an expression if it can deduce that that expression’s value is not used and that no needed side effects are produced (including any caused by calling a method or accessing a volatile field). When program execution is interrupted by an asynchronous event (such as an exception thrown by another thread), it is not guaranteed that the observable side effects are visible in the original program order.