A field is a member that represents a variable associated with an object or
class. A field-declaration introduces
one or more fields of a given type.
field-declaration:
attributesopt field-modifiersopt type variable-declarators ;
field-modifiers:
field-modifier
field-modifiers field-modifier
Chapter 17 Classes
221
field-modifier:
new
public
protected
internal
private
static
readonly
volatile
variable-declarators:
variable-declarator
variable-declarators , variable-declarator
variable-declarator:
identifier
identifier = variable-initializer
variable-initializer:
expression
array-initializer
A field-declaration may include a set of attributes (§24), a new modifier (
§17.2.2), a valid combination of the
four access modifiers (§17.2.3), and a static modifier (§17.4.1). In
addition, a field-declaration may include a
readonly modifier (§17.4.2) or a volatile modifier (§17.4.3), but not
both The attributes and modifiers apply
to all of the members declared by the field-declaration. It is an error for
the same modifier to appear multiple
times in a field declaration.
The type of a field-declaration specifies the type of the members
introduced by the declaration. The type is
followed by a list of variable-declarators, each of which introduces a new
member. A variable-declarator
consists of an identifier that names that member, optionally followed by an
?=? token and a variable-initializer
(§17.4.5) that gives the initial value of that member.
The type of a field must be at least as accessible as the field itself (§10.
5.4).
The value of a field is obtained in an expression using a simple-name (§14.5
.2) or a member-access (§14.5.4).
The value of a non-readonly field is modified using an assignment (§14.13).
The value of a non-readonly field
can be both obtained and modified using postfix increment and decrement
operators (§14.5.9) and prefix
increment and decrement operators (§14.6.5).
A field declaration that declares multiple fields is equivalent to multiple
declarations of single fields with the
same attributes, modifiers, and type. [Example: For example
class A
{
public static int X = 1, Y, Z = 100;
}
is equivalent to
class A
{
public static int X = 1;
public static int Y;
public static int Z = 100;
}
end example]
17.4.1 Static and instance fields
When a field declaration includes a static modifier, the fields introduced
by the declaration are static fields.
When no static modifier is present, the fields introduced by the
declaration are instance fields. Static fields
C# LANGUAGE SPECIFICATION
222
and instance fields are two of the several kinds of variables (§12)
supported by C#, and at times they are referred
to as static variables and instance variables, respectively.
A static field is not part of a specific instance; instead, it identifies
exactly one storage location. No matter how
many instances of a class are created, there is only ever one copy of a
static field for the associated application
domain.
An instance field belongs to an instance. Specifically, every instance of a
class contains a separate set of all the
instance fields of that class.
When a field is referenced in a member-access (§14.5.4) of the form E.M,
if M is a static field, E must denote a
type that has a field M, and if M is an instance field, E must denote an
instance of a type that has a field M.
The differences between static and instance members are discussed further
in §17.2.5.
17.4.2 Readonly fields
When a field-declaration includes a readonly modifier, the fields
introduced by the declaration are readonly
fields. Direct assignments to readonly fields can only occur as part of
that declaration or in an instance
constructor or static constructor in the same class. (A readonly field can
be assigned to multiple times in these
contexts.) Specifically, direct assignments to a readonly field are
permitted only in the following contexts:
? In the variable-declarator that introduces the field (by including a
variable-initializer in the declaration).
? For an instance field, in the instance constructors of the class that
contains the field declaration; for a static
field, in the static constructor of the class that contains the field
declaration. These are also the only contexts
in which it is valid to pass a readonly field as an out or ref parameter.
Attempting to assign to a readonly field or pass it as an out or ref
parameter in any other context is a
compile-time error.
17.4.2.1 Using static readonly fields for constants
A static readonly field is useful when a symbolic name for a constant value
is desired, but when the type of
the value is not permitted in a const declaration, or when the value cannot
be computed at compile-time.
[Example: In the example
public class Color
{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);
private byte red, green, blue;
public Color(byte r, byte g, byte b) {
red = r;
green = g;
blue = b;
}
}
the Black, White, Red, Green, and Blue members cannot be declared as const
members because their values
cannot be computed at compile-time. However, declaring them static readonly
instead has much the same
effect. end example]
17.4.2.2 Versioning of constants and static readonly fields
Constants and readonly fields have different binary versioning semantics.
When an expression references a
constant, the value of the constant is obtained at compile-time, but when
an expression references a readonly
field, the value of the field is not obtained until run-time. [Example:
Consider an application that consists of two
separate programs:
Chapter 17 Classes
223
using System;
namespace Program1
{
public class Utils
{
public static readonly int X = 1;
}
}
namespace Program2
{
class Test
{
static void Main() {
Console.WriteLine(Program1.Utils.X);
}
}
}
The Program1 and Program2 namespaces denote two programs that are compiled
separately. Because
Program1.Utils.X is declared as a static readonly field, the value output
by the Console.WriteLine
statement is not known at compile-time, but rather is obtained at run-time.
Thus, if the value of X is changed and
Program1 is recompiled, the Console.WriteLine statement will output the new
value even if Program2 isn?t
recompiled. However, had X been a constant, the value of X would have been
obtained at the time Program2 was
compiled, and would remain unaffected by changes in Program1 until Program2
is recompiled. end example]
17.4.3 Volatile fields
When a field-declaration includes a volatile modifier, the fields
introduced by that declaration are volatile
fields. For non-volatile fields, optimization techniques that reorder
instructions can lead to unexpected and
unpredictable results in multi-threaded programs that access fields without
synchronization such as that provided
by the lock-statement (§15.12). These optimizations can be performed by
the compiler, by the runtime system, or
by hardware. For volatile fields, such reordering optimizations are
restricted:
? A read of a volatile field is called a volatile read. A volatile read has
?acquire semantics?; that is, it is
guaranteed to occur prior to any references to memory that occur after it
in the instruction sequence.
? A write of a volatile field is called a volatile write. A volatile write
has ?release semantics?; that is, it is
guaranteed to happen after any memory references prior to the write
instruction in the instruction sequence.
These restrictions ensure that all threads will observe volatile writes
performed by any other thread in the order in
which they were performed. A conforming implementation is not required to
provide a single total ordering of
volatile writes as seen from all threads of execution. The type of a
volatile field must be one of the following:
? A reference-type.
? The type byte, sbyte, short, ushort, int, uint, char, float, or bool.
? An enum-type having an enum base type of byte, sbyte, short, ushort, int,
or uint.
[Example: The example
using System;
using System.Threading;
class Test
{
public static int result;
public static volatile bool finished;
static void Thread2() {
result = 143;
finished = true;
}
C# LANGUAGE SPECIFICATION
224
static void Main() {
finished = false;
// Run Thread2() in a new thread
new Thread(new ThreadStart(Thread2)).Start();
// Wait for Thread2 to signal that it has a result by setting
// finished to true.
for (;;) {
if (finished) {
Console.WriteLine("result = {0}", result);
return;
}
}
}
}
produces the output:
result = 143
In this example, the method Main starts a new thread that runs the method
Thread2. This method stores a value
into a non-volatile field called result, then stores true in the volatile
field finished. The main thread waits
for the field finished to be set to true, then reads the field result.
Since finished has been declared
volatile, the main thread must read the value 143 from the field result. If
the field finished had not been
declared volatile, then it would be permissible for the store to result to
be visible to the main thread after
the store to finished, and hence for the main thread to read the value 0
from the field result. Declaring
finished as a volatile field prevents any such inconsistency. end example]
17.4.4 Field initialization
The initial value of a field, whether it be a static field or an instance
field, is the default value (§12.2) of the
field?s type. It is not possible to observe the value of a field before
this default initialization has occurred, and a
field is thus never ?uninitialized?. [Example: The example
using System;
class Test
{
static bool b;
int i;
static void Main() {
Test t = new Test();
Console.WriteLine("b = {0}, i = {1}", b, t.i);
}
}
produces the output
b = False, i = 0
because b and i are both automatically initialized to default values. end
example]
17.4.5 Variable initializers
Field declarations may include variable-initializers. For static fields,
variable initializers correspond to
assignment statements that are executed during class initialization. For
instance fields, variable initializers
correspond to assignment statements that are executed when an instance of
the class is created.
[Example: The example
using System;
class Test
{
static double x = Math.Sqrt(2.0);
int i = 100;
string s = "Hello";
Chapter 17 Classes
225
static void Main() {
Test a = new Test();
Console.WriteLine("x = {0}, i = {1}, s = {2}", x, a.i, a.s);
}
}
produces the output
x = 1.4142135623731, i = 100, s = Hello
because an assignment to x occurs when static field initializers execute
and assignments to i and s occur when
the instance field initializers execute. end example]
The default value initialization described in §17.4.3 occurs for all
fields, including fields that have variable
initializers. Thus, when a class is initialized, all static fields in that
class are first initialized to their default values,
and then the static field initializers are executed in textual order.
Likewise, when an instance of a class is created,
all instance fields in that instance are first initialized to their default
values, and then the instance field initializers
are executed in textual order.
It is possible for static fields with variable initializers to be observed
in their default value state. [Example:
However, this is strongly discouraged as a matter of style. The example
using System;
class Test
{
static int a = b + 1;
static int b = a + 1;
static void Main() {
Console.WriteLine("a = {0}, b = {1}", a, b);
}
}
exhibits this behavior. Despite the circular definitions of a and b, the
program is valid. It results in the output
a = 1, b = 2
because the static fields a and b are initialized to 0 (the default value
for int) before their initializers are
executed. When the initializer for a runs, the value of b is zero, and so a
is initialized to 1. When the initializer
for b runs, the value of a is already 1, and so b is initialized to 2. end
example]
17.4.5.1 Static field initialization
The static field variable initializers of a class correspond to a sequence
of assignments that are executed in the
textual order in which they appear in the class declaration. If a static
constructor (§17.11) exists in the class,
execution of the static field initializers occurs immediately prior to
executing that static constructor. Otherwise,
the static field initializers are executed at an implementation-dependent
time prior to the first use of a static field
of that class. [Example: The example
using System;
class Test
{
static void Main() {
Console.WriteLine("{0} {1}", B.Y, A.X);
}
public static int f(string s) {
Console.WriteLine(s);
return 1;
}
}
class A
{
public static int X = Test.f("Init A");
}
C# LANGUAGE SPECIFICATION
226
class B
{
public static int Y = Test.f("Init B");
}
might produce either the output:
Init A
Init B
1 1
or the output:
Init B
Init A
1 1
because the execution of X’s initializer and Y’s initializer could occur
in either order; they are only constrained to
occur before the references to those fields. However, in the example:
using System;
class Test
{
static void Main() {
Console.WriteLine("{0} {1}", B.Y, A.X);
}
public static int f(string s) {
Console.WriteLine(s);
return 1;
}
}
class A
{
static A() {}
public static int X = Test.f("Init A");
}
class B
{
static B() {}
public static int Y = Test.f("Init B");
}
the output must be:
Init B
Init A
1 1
because the rules for when static constructors execute provide that B’s
static constructor (and hence B’s static field
initializers) must run before A’s static constructor and field
initializers. end example]
17.4.5.2 Instance field initialization
The instance field variable initializers of a class correspond to a
sequence of assignments that are executed
immediately upon entry to any one of the instance constructors (§17.10.2)
of that class. The variable initializers
are executed in the textual order in which they appear in the class
declaration. The class instance creation and
initialization process is described further in §17.10.
A variable initializer for an instance field cannot reference the instance
being created. Thus, it is a compile-time
error to reference this in a variable initializer, as it is a compile-time
error for a variable initializer to reference
any instance member through a simple-name. [Example: In the example
class A
{
int x = 1;
int y = x + 1; // Error, reference to instance member of this
}
Chapter 17 Classes
227
the variable initializer for y results in a compile-time error because it
references a member of the instance being
created. end example]