Events
17 Mar, 9 pm - 21 Mar, 10 am
Join the meetup series to build scalable AI solutions based on real-world use cases with fellow developers and experts.
Register nowThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
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.
A program compiled as an application shall contain at least one method qualifying as an entry point by satisfying the following requirements:
Main
.static
.async
modifier provided the method’s return type is System.Threading.Tasks.Task
or System.Threading.Tasks.Task<int>
.void
, int
, System.Threading.Tasks.Task
, or System.Threading.Tasks.Task<int>
.string[]
.Note: Methods with the
async
modifier must have exactly one of the two return types specified above in order to qualify as an entry point. Anasync void
method, or anasync
method returning a different awaitable type such asValueTask
orValueTask<int>
does not qualify as an entry point. end note
If more than one method qualifying as an entry point is declared within a program, an external mechanism may be used to specify which method is deemed to be the actual entry point for the application. If a qualifying method having a return type of int
or void
is found, any qualifying method having a return type of System.Threading.Tasks.Task
or System.Threading.Tasks.Task<int>
is not considered an entry point method. It is a compile-time error for a program to be compiled as an application without exactly one entry point. A program compiled as a class library may contain methods that would qualify as application entry points, but the resulting library has no entry point.
Ordinarily, the declared accessibility (§7.5.2) of a method is determined by the access modifiers (§15.3.6) specified in its declaration, and similarly the declared accessibility of a type is determined by the access modifiers specified in its declaration. In order for a given method of a given type to be callable, both the type and the member shall be accessible. However, the application entry point is a special case. Specifically, the execution environment can access the application’s entry point regardless of its declared accessibility and regardless of the declared accessibility of its enclosing type declarations.
When the entry point method has a return type of System.Threading.Tasks.Task
or System.Threading.Tasks.Task<int>
, a compiler shall synthesize a synchronous entry-point method that calls the corresponding Main
method. The synthesized method has parameters and return types based on the Main
method:
Main
methodMain
method is System.Threading.Tasks.Task
, the return type of the synthesized method is void
Main
method is System.Threading.Tasks.Task<int>
, the return type of the synthesized method is int
Execution of the synthesized method proceeds as follows:
Main
method, passing its string[]
parameter value as an argument if the Main
method has such a parameter.Main
method throws an exception, the exception is propagated by the synthesized method.GetAwaiter().GetResult()
on the task, using either the parameterless instance method or the extension method described by §C.3. If the task fails, GetResult()
will throw an exception, and this exception is propagated by the synthesized method.Main
method with a return type of System.Threading.Tasks.Task<int>
, if the task completes successfully, the int
value returned by GetResult()
is returned from the synthesized method.The effective entry point of an application is the entry point declared within the program, or the synthesized method if one is required as described above. The return type of the effective entry point is therefore always void
or int
.
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.
Application startup occurs when the execution environment calls the application’s effective entry point. If the effective entry point declares a parameter, then during application startup, the implementation shall ensure that the initial value of that parameter is a non-null reference to a string array. This array shall consist of non-null references to strings, called application parameters, which are given implementation-defined values by the host environment prior to application startup. The intent is to supply to the application information determined prior to application startup from elsewhere in the hosted environment.
Note: On systems supporting a command line, application parameters correspond to what are generally known as command-line arguments. end note
If the effective entry point’s return type is int
, the return value from the method invocation by the execution environment is used in application termination (§7.2).
Other than the situations listed above, entry point methods behave like those that are not entry points in every respect. In particular, if the entry point is invoked at any other point during the application’s lifetime, such as by regular method invocation, there is no special handling of the method: if there is a parameter, it may have an initial value of null
, or a non-null
value referring to an array that contains null references. Likewise, the return value of the entry point has no special significance other than in the invocation from the execution environment.
Application termination returns control to the execution environment.
If the return type of the application’s effective entry point method is int
and execution completes without resulting in an exception, the value of the int
returned serves as the application’s termination status code. The purpose of this code is to allow communication of success or failure to the execution environment. If the return type of the effective entry point method is void
and execution completes without resulting in an exception, the termination status code is 0
.
If the effective entry point method terminates due to an exception (§21.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
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.7) are used to define classes (§15), structs (§16), interfaces (§18), enums (§19), and delegates (§20). 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:
Note: However, these declarations could introduce ambiguities if included in the same application. end note
There are several different types of declaration spaces, as described in the following.
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.7.1) by the nested local name.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 variablez
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.C#
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:
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
C#
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
andMegacorp.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
andG
methods result in a compile-time error because the namei
is declared in the outer block and cannot be redeclared in the inner block. However, theH
andI
methods are valid since the twoi
’s are declared in separate non-nested blocks.C#
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
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 isn’t 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.5.2). end note
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.
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).
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
.
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.
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 (§18.4). However, the members in classobject
are available via member lookup in any interface type (§12.5). end note
The members of an array are the members inherited from class System.Array
.
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 (§20.2). An invocation of this method shall behave identically to a delegate invocation (§20.6) on the same delegate instance.
An implementation may provide additional members, either through inheritance or directly in the delegate itself.
Declarations of members allow control over member access. The accessibility of a member is established by the declared accessibility (§7.5.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.5.3) of the member.
The declared accessibility of a member can be one of the following:
public
modifier in the member declaration. The intuitive meaning of public
is “access not limited”.protected
modifier in the member declaration. The intuitive meaning of protected
is “access limited to the containing class or types derived from the containing class”.internal
modifier in the member declaration. The intuitive meaning of internal
is “access limited to this assembly”.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
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
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.
public
declared accessibility. No access modifiers are allowed on namespace declarations.public
or internal
declared accessibility and default to internal
declared accessibility.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
orinternal
declared accessibility. end note
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
, orprivate
declared accessibility, whereas a type declared as a member of a namespace can have onlypublic
orinternal
declared accessibility. end note
public
declared accessibility. No access modifiers are allowed on interface member declarations.public
declared accessibility. No access modifiers are allowed on enumeration member declarations.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:
T
is public, the accessibility domain of T
is the program text of P
and any program that references P
.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):
M
is public
, the accessibility domain of M
is the accessibility domain of T
.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
.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
.M
is protected
, let D
be the union of the program text of T
and 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
.M
is internal
, the accessibility domain of M
is the intersection of the accessibility domain of T
with the program text of P
.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
ispublic
, the access is permitted.- Otherwise, if
M
isprotected internal
, the access is permitted if it occurs within the program in whichM
is declared, or if it occurs within a class derived from the class in whichM
is declared and takes place through the derived class type (§7.5.4).- Otherwise, if
M
isprotected
, the access is permitted if it occurs within the class in whichM
is declared, or if it occurs within a class derived from the class in whichM
is declared and takes place through the derived class type (§7.5.4).- Otherwise, if
M
isinternal
, the access is permitted if it occurs within the program in whichM
is declared.- Otherwise, if
M
isprivate
, the access is permitted if it occurs within the type in whichM
is declared.- Otherwise, the type or member is inaccessible, and a compile-time error occurs. end note
Example: In the following code
C#
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
andA.X
is unlimited.- The accessibility domain of
A.Y
,B
,B.X
,B.Y
,B.C
,B.C.X
, andB.C.Y
is the program text of the containing program.- The accessibility domain of
A.Z
is the program text ofA
.- The accessibility domain of
B.Z
andB.D
is the program text ofB
, including the program text ofB.C
andB.D
.- The accessibility domain of
B.C.Z
is the program text ofB.C
.- The accessibility domain of
B.D.X
andB.D.Y
is the program text ofB
, including the program text ofB.C
andB.D
.- The accessibility domain of
B.D.Z
is the program text ofB.D
. As the example illustrates, the accessibility domain of a member is never larger than that of a containing type. For example, even though allX
members have public declared accessibility, all butA.X
have accessibility domains that are constrained by a containing type.end example
As described in §7.4, 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
C#
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 memberx
from theA
class. Because the member is private, it is only accessible within the class_body ofA
. Thus, the access tob.x
succeeds in theA.F
method, but fails in theB.F
method.end example
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.
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 of D
, access to M
can take one of the following forms:
M
.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
.base.M
.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
C#
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 accessx
through instances of bothA
andB
, since in either case the access takes place through an instance ofA
or a class derived fromA
. However, withinB
, it is not possible to accessx
through an instance ofA
, sinceA
does not derive fromB
.end example
Example:
C#
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.5.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:
C#
class C<T> { protected static T x; } class D : C<string> { static void Main() { C<int>.x = 5; } }
the reference to
protected
memberC<int>.x
inD
is valid even though the classD
derives fromC<string>
. end note
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:
Example: In the following code
C#
class A {...} public class B: A {...}
the
B
class results in a compile-time error becauseA
is not at least as accessible asB
.end example
Example: Likewise, in the following code
C#
class A {...} public class B { A F() {...} internal A G() {...} public A H() {...} }
the
H
method inB
results in a compile-time error because the return typeA
is not at least as accessible as the method.end example
Methods, instance constructors, indexers, and operators are characterized by their signatures:
params
or this
parameter modifiers, nor whether parameters are required or optional.params
modifier that may be specified for the right-most parameter, nor whether parameters are required or optional.params
modifier that may be specified for the right-most parameter, nor whether parameters are required or optional.Signatures are the enabling mechanism for overloading of members in classes, structs, and interfaces:
Although in
, out
, and ref
parameter modifiers are considered part of a signature, members declared in a single type cannot differ in signature solely by in
, out
, and ref
. 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), in
, out
, and ref
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 the Common Language Infrastructure (CLI), which does not provide a way to define methods that differ solely in
in
,out
, andref
. 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.
C#
interface ITest { void F(); // F() void F(int x); // F(int) void F(ref int x); // F(ref int) 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
in
,out
, andref
parameter modifiers (§15.6.2) are part of a signature. Thus,F(int)
,F(in int)
,F(out int)
, andF(ref int)
are all unique signatures. However,F(in int)
,F(out int)
, andF(ref int)
cannot be declared within the same interface because their signatures differ solely byin
,out
, andref
. Also, note that the return type and theparams
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 theparams
modifier. As such, the declarations of the methodsF(int)
andF(params string[])
identified above, result in a compile-time error. end example
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.3 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.
The scope of a namespace member declared by a namespace_member_declaration (§14.6) with no enclosing namespace_declaration is the entire program text.
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.
The scope of a name defined by an extern_alias_directive (§14.4) extends over the using_directives, global_attributes and namespace_member_declarations 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 or namespace_body in which it occurs.
The scope of a name defined or imported by a using_directive (§14.5) extends over the global_attributes 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 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 of that struct_declaration.
The scope of a type parameter declared by a type_parameter_list on an interface_declaration (§18.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 (§20.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.
The scope of a member declared by a class_member_declaration (§15.3.1) is the class_body in which the declaration occurs. In addition, the scope of a class member extends to the class_body of those derived classes that are included in the accessibility domain (§7.5.3) of the member.
The scope of a member declared by a struct_member_declaration (§16.3) is the struct_body in which the declaration occurs.
The scope of a member declared by an enum_member_declaration (§19.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.
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.
The scope of a parameter declared in a lambda_expression (§12.19) is the lambda_expression_body of that lambda_expression.
The scope of a parameter declared in an anonymous_method_expression (§12.19) is the block of that anonymous_method_expression.
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:
C#
class A { void F() { i = 1; } int i = 0; }
Here, it is valid for
F
to refer toi
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:
C#
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 toi
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 theG
method, the use ofj
in the initializer for the declaration ofj
is valid because the use does not precede the declarator. In theH
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
C#
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 variableA
and in a type context to refer to the classA
.end note
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.
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
C#
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 variablei
is hidden by the local variablei
, but within theG
method,i
still refers to the instance variable. Inside the local functionM1
thefloat i
hides the immediate-outeri
. The lambda parameteri
hides thefloat 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
C#
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 theF
declared inInner
because all outer occurrences ofF
are hidden by the inner declaration. For the same reason, the callF("Hello")
results in a compile-time error.end example
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:
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
C#
class Base { public void F() {} } class Derived : Base { public void F() {} // Warning, hiding an inherited name }
the declaration of
F
inDerived
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 ofBase
introduced anF
method that wasn’t 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:
C#
class Base { public void F() {} } class Derived : Base { public new void F() {} }
The
new
modifier indicates that theF
inDerived
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:
C#
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
inDerived
hides theF
that was inherited fromBase
, but since the newF
inDerived
has private access, its scope does not extend toMoreDerived
. Thus, the callF()
inMoreDerived.G
is valid and will invokeBase.F
.end example
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?
| namespace_or_type_name '.' identifier type_argument_list?
| qualified_alias_member
;
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.
If the namespace_or_type_name is a qualified_alias_member its meaning is as described in §14.8.1. Otherwise, a namespace_or_type_name has one of four forms:
I
I<A₁, ..., Aₓ>
N.I
N.I<A₁, ..., Aₓ>
where I
is a single identifier, N
is a namespace_or_type_name and <A₁, ..., Aₓ>
is an optional type_argument_list. When no type_argument_list is specified, consider x
to be zero.
The meaning of a namespace_or_type_name is determined as follows:
I
or of the form I<A₁, ..., Aₓ>
:
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 the namespace_or_type_name refers to that type parameter.T
(§15.3.2), starting with the instance type of that type declaration and continuing with the instance type of each enclosing class or struct declaration (if any):
x
is zero and the declaration of T
includes a type parameter with name I
, then the namespace_or_type_name refers to that type parameter.T
or any of its base types contain a nested accessible type having name I
and x
type parameters, then the namespace_or_type_name 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.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
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:
x
is zero and I
is the name of a namespace in N
, then:
N
and the namespace declaration contains an extern_alias_directive or 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.I
in N
.N
contains an accessible type having name I
and x
type parameters, then:
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, then the namespace_or_type_name is ambiguous and a compile-time error occurs.N
:
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, then the namespace_or_type_name refers to that namespace or type.I
and x
type parameters, then the namespace_or_type_name refers to that type constructed with the given type arguments.I
and x
type parameters, then the namespace_or_type_name is ambiguous and an error occurs.N.I
or of the form N.I<A₁, ..., Aₓ>
. N
is first resolved as a namespace_or_type_name. If the resolution of N
is not successful, a compile-time error occurs. Otherwise, N.I
or N.I<A₁, ..., Aₓ>
is resolved as follows:
x
is zero and N
refers to a namespace and N
contains a nested namespace with name I
, then the namespace_or_type_name refers to that nested namespace.N
refers to a namespace and N
contains an accessible type having name I
and x
type parameters, then the namespace_or_type_name refers to that type constructed with the given type arguments.N
refers to a (possibly constructed) class or struct type and N
or any of its base classes contain a nested accessible type having name I
and x
type parameters, then the namespace_or_type_name 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.
Note: If the meaning of
N.I
is being determined as part of resolving the base class specification ofN
then the direct base class ofN
is considered to beobject
(§15.2.4.2). end note
N.I
is an invalid namespace_or_type_name, and a compile-time error occurs.A namespace_or_type_name is permitted to reference a static class (§15.2.2.4) only if
T
in a namespace_or_type_name of the form T.I
, orT
in a typeof_expression (§12.8.18) of the form typeof(T)
Every namespace declaration and type declaration has an unqualified name determined as follows:
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:
N
is a member of the global namespace, its fully qualified name is N
.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:
Example: The example below shows several namespace and type declarations along with their associated fully qualified names.
C#
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
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:
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
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
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.2).
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
C#
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 classB
. These objects become eligible for garbage collection when the variableb
is assigned the valuenull
, since after this time it is impossible for any user-written code to access them. The output could be eitherConsoleFinalize instance of A Finalize instance of B
or
ConsoleFinalize 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,
C#
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 ofB
, then the output of this program might be:ConsoleFinalize instance of A Finalize instance of B A.F RefA is not null
Note that although the instance of
A
was not in use andA
’s finalizer was run, it is still possible for methods ofA
(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 ofB
’s finalizer caused an instance ofA
that was previously not in use, to become accessible from the live referenceTest.RefA
. After the call toWaitForPendingFinalizers
, the instance ofB
is eligible for collection, but the instance ofA
is not, because of the referenceTest.RefA
.end example
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:
ECMA C# draft specification feedback
ECMA C# draft specification is an open source project. Select a link to provide feedback:
Events
17 Mar, 9 pm - 21 Mar, 10 am
Join the meetup series to build scalable AI solutions based on real-world use cases with fellow developers and experts.
Register now