attributes
?
access-modifier
?
|
new?
[
abstract
|
sealed
]?
|
class
class-name
[
|
: base-class | :
interface
+ |
:
base-class
,
interface
+
]?
|
{
class-members
}
|
Struct declaration syntax:
attributes
?
access-modifier
?
|
new?
|
struct
struct-name
[
:
interface
+
]?
|
{
struct-members
}
|
A class or struct combines data, functions, and nested types into a new type, which is a key building block of C# applications. The body of a class or struct is comprised of three kinds of members: data, function, and type.
Includes fields, constants, events. The most common data members are fields. Events are a special case, since they combine data and functionality in the class or struct (see Section 2.14).
Includes methods, properties, indexers, operators, constructors, and destructors. Note that all function members are either specialized types of methods or are implemented with one or more specialized types of methods.
Includes nested types. Types can be nested to control their accessibility (see Section 2.8).
Here’s an example:
class ExampleClass { int x; // data member void Foo( ) {} // function member struct MyNestedType {} // type member }
Classes differ from structs in the following ways:
A class is a reference type; a struct is a value type. Consequently, structs typically represent simple types, whereby value-type semantics are desirable (e.g., assignment copies a value rather than a reference).
A class fully supports inheritance (see Section 2.7). A struct inherits from
object
and is implicitly sealed. Both classes and
structs can implement interfaces.
A class can have a destructor; a struct can’t.
A class can define a custom parameterless constructor and initialize instance fields; a struct can’t. The default parameterless constructor for a struct initializes each field with a default value (effectively zero). If a struct declares a constructor(s), all its fields must be assigned in that constructor call.
Data
members and function members may
be either instance (default) or static members. Instance members are
associated with an instance of a type, whereas static members are
associated with the type itself. Furthermore, invocation of static
members from outside their enclosing type requires specifying the
type name. In this example, the instance method
PrintName
prints the name of a particular
Panda
, while the static method
PrintSpeciesName
prints the name shared by all
Panda
s in the application
(AppDomain
):
class Panda { string name; static string speciesName = "Ailuropoda melanoleuca"; // Initializes Panda(see Instance Constructors) public Panda(string name) { this.name = name; } public void PrintName( ) { Console.WriteLine(name); } public static void PrintSpeciesName( ) { Console.WriteLine(speciesName); } } class Test { static void Main( ) { Panda.PrintSpeciesName( ); // invoke static method Panda p = new Panda("Petey"); p.PrintName( ); // invoke instance method } }
attributes
?
access-modifier
?
|
new?
|
static?
|
readonly?
|
type
[
field-name
[ =
expression
]?
]+ ;
|
Fields hold data for a class or struct. Fields are also referred to as member variables:
class MyClass { int x; float y = 1, z = 2; static readonly int MaxSize = 10; ... }
As the name suggests, the readonly
modifier
ensures a field can’t be modified after it’s assigned.
Such a field is termed a read-only field. A
read-only field is always evaluated at runtime, not at compile time.
To compile, a nonread-only field must be assigned in its declaration
or within the type’s constructor (see Section 2.9.9). Nonread-only fields merely
generate a warning when left unassigned.
A constant is a field that is evaluated at compile time and is implicitly static. The logical consequence of this is that a constant can’t defer evaluation to a method or constructor and can only be one of a few built-in types (see the preceding syntax definition).
public const double PI = 3.14159265358979323846;
The benefit of a constant is that since it is evaluated at compile time, the compiler can perform additional optimization. For instance:
public static double Circumference(double radius) { return 2 * Math.PI * radius; }
Evaluates to:
public static double Circumference(double radius) { return 6.2831853071795862 * radius; }
A readonly
field
isn’t optimized by the compiler but is more versionable. For
instance, suppose there is a mistake with PI
, and
Microsoft releases a patch to their library that contains the
Math
class, which is deployed to each client
computer. If software using the Circumference
method is already deployed on a client machine, the mistake
isn’t fixed until you recompile your application with the
latest version of the Math
class. With a
readonly
field, however, this mistake is
automatically fixed the next time the client application is executed.
Generally this scenario occurs when a field value changes not as a
result of a mistake, but simply because of an upgrade, such as a
change in the value of the MaxThreads
constant
from 500 to 1000.
attributes
?
access-modifier
?
|
[override
|
new?
[
virtual
|
abstract
|
static]?
]?
|
unsafe?
|
type property-name { [
|
attributes
?
get
statement-block
| //
read-only
|
attributes
?
set
statement-block
| //
write-only
|
attributes
?
get
statement-block
//
read-write
|
attributes
?
set
statement-block
|
|
] }
|
abstract accessors don’t specify an
implementation, so they replace a statement block with a semicolon.
Also see Section 2.8.1. |
A property can be characterized as an object-oriented field. Properties promote encapsulation by allowing a class or struct to control access to its data and by hiding the internal representation of the data. For instance:
public class Well { decimal dollars; // private field public int Cents { get { return(int)(dollars * 100); } set { // value is an implicit variable in a set if (value>=0) // typical validation code dollars = (decimal)value/100; } } } class Test { static void Main( ) { Well w = new Well( ); w.Cents = 25; // set int x = w.Cents; // get w.Cents += 10; // get and set(throw a dime in the well) } }
The get
accessor returns a value of the
property’s type. The set
accessor has an
implicit parameter value
that is of the
property’s type.
Many languages loosely implement
properties with a get
or set
method convention, and in fact C# properties are compiled to
get_XXX
or set_XXX
methods,
which is their representation in MSIL; for example:
public int get_Cents {...} public void set_Cents (int value) {...}
Simple property accessors are inlined by the JIT (just-in-time compiler), which means there is no performance difference between a property access and a field access. Inlining is an optimization that replaces a method call with the body of that method.
attributes
?
access-modifier
?
|
[override
|
new?
[virtual
|
abstract]?]?
|
unsafe?
|
type
this
[
attributes
?
[
type arg
]+ ] {
|
attributes
?
get
statement-block
| //
read-only
attributes
?
set
statement-block
| //
write-only
|
attributes
?
get
statement-block
//
read-write
|
attributes
?
set
statement-block
|
}
|
abstract accessors don’t specify an
implementation, so they replace a statement block with a semicolon.
Also see Section 2.8.1. |
An
indexer
provides a natural way to index
elements in a class or struct that encapsulates a collection, using
the open and closed bracket []
syntax of the array
type. For example:
public class ScoreList { int[] scores = new int [5]; // indexer public int this[int index] { get { return scores[index]; } set { if(value >= 0 && value <= 10) scores[index] = value; } } // property (read-only) public int Average { get { int sum = 0; foreach(int score in scores) sum += score; return sum / scores.Length; } } } class IndexerTest { static void Main( ) { ScoreList sl = new ScoreList( ); sl[0] = 9; sl[1] = 8; sl[2] = 7; sl[3] = sl[4] = sl[1]; System.Console.WriteLine(sl.Average); } }
A type may declare multiple indexers that take different parameters.
attributes
?
access-modifier
?
|
[
override
|
new?
[ virtual
|
abstract
|
static
extern? ]? ]?
|
unsafe?
|
[ void
|
type
]
method-name
(parameter-list ) |
statement-block
|
Parameter list syntax:
[
attributes
?
[ref
|
out]?
type arg
]
*
|
[
params
attributes
?
type
[ ]
arg
]?
|
abstract
and extern
methods
don’t contain a method body. Also see Section 2.8.1.
All C# code executes in a method or a special
form of a method (constructors, destructors, and operators are
special types of methods, and properties and indexers are internally
implemented with get
/set
methods).
A method’s
signature is characterized by the type and
modifier of each parameter in its parameter list. The parameter
modifiers ref
and out
allow
arguments to be passed by reference rather than be passed by value.
By default, arguments in C# are passed by value, which is by far the most common case. This means a copy of the value is created when passed to the method:
static void Foo(int p) {++p;} static void Main( ) { int x = 8; Foo(x); // make a copy of the value type x Console.WriteLine(x); // x will still be 8 }
Assigning p
a new value doesn’t change the
contents of x
, since p
and
x
reside in different memory
locations.
To pass by
reference, C# provides the parameter modifier ref
.
Using this modifier allows p
and
x
to refer to the same memory locations:
static void Foo(ref int p) {++p;} static void Test( ) { int x = 8; Foo(ref x); // send reference of x to Foo Console.WriteLine(x); // x is now 9 }
Now, assigning p
a new value changes the contents
of x
. This is usually why you want to pass by
reference, though occasionally it is an efficient technique with
which to pass large structs. Notice how the ref
modifier is required in the method call, as well as in the method
declaration. This makes it very clear what’s going on and also
removes ambiguity since parameter modifiers change the signature of a
method (see Section 2.9.7.1).
C# is a
language that enforces the requirement that variables be assigned
before use, so it also provides the out
modifier,
which is the natural complement of the ref
modifier. While a ref
modifier requires that a
variable be assigned a value before being passed to a method, the
out
modifier requires that a variable be assigned
a value before returning from a method:
using System; class Test { static void Split(string name, out string firstNames, out string lastName) { int i = name.LastIndexOf(' '), firstNames = name.Substring(0, i); lastName = name.Substring(i+1); } static void Main( ) { string a, b; Split("Nuno Bettencourt", out a, out b); Console.WriteLine("FirstName:{0}, LastName:{1}", a, b); } }
The params
parameter modifier may be specified on
the last parameter of a method so that the method can accept any
number of parameters of a particular type. For example:
using System; class Test { static int Add(params int[] iarr) { int sum = 0; foreach(int i in iarr) sum += i; return sum; } static void Main( ) { int i = Add(1, 2, 3, 4); Console.WriteLine(i); // 10 } }
A type may overload methods (have multiple methods with the same name) so long as the signatures are different. For example, the following methods can coexist in the same type:
void Foo(int x); viod Foo(double x); void Foo(int x, float y); void Foo(float x, int y); void Foo(ref int x); void Foo(out int x);
However, the following pairs of methods can’t coexist in the
same type, since the return
type and
params
modifier don’t qualify as part of a
method’s signature.
void Foo(int x); float Foo(int x); // compile error void Goo (int[] x); void Goo (params int[] x); // compile error
*
(binary only) / % &
(binary only)
|
^
<<
>>
++
!= >
<
>=
<=
Literals doubling as overloadable operators:
true
false
|
C# lets you overload operators to work with operands that are custom
classes or structs using operators. An operator
is a static method with the keyword operator
preceding the operator to be overloaded (instead of a method name),
parameters representing the operands, and return type representing
the result of an expression.
The most common operators to overload are the ==
and !=
operators, which are used to implement
value equality, as opposed to referential equality. If two objects
have the same value, they are considered equivalent, even if they
refer to objects in different memory locations.
In the following scenario you should override the virtual
Equals
method to route its functionality to the
==
operator. This allows a class to be later
treated as one of its base classes (see Section 2.7.2), yet still be tested for value
equality. It also provides compatibility with other .NET languages
that don’t overload operators.
class Note int value; public Note(int semitonesFromA) { value = semitonesFromA; } public static bool operator ==(Note x, Note y) { return x.value == y.value; } public static bool operator !=(Note x, Note y) { return x.value != y.value; } public override bool Equals(object o) { if(!(o is Note)) return false; return this ==(Note)o; } } Note a = new Note(4); Note b = new Note(4); Object c = a; Object d = b; // To compare a and b by reference Console.WriteLine((object)a ==(object)b; // false //To compare a and b by value: Console.WriteLine(a == b); // true //To compare c and d by reference: Console.WriteLine(c == d); // false //To compare c and d by value: Console.WriteLine(c.Equals(d)); // true
The C# compiler enforces the rule that operators that are logical
pairs must both be defined. These operators are ==
and !=
; <
and
>
; and <=
and
>=
.
As explained in Section 2.2, the
rationale behind
implicit
conversions is they are
guaranteed to succeed and not lose information during the conversion.
Conversely, an explicit conversion is required
either when runtime circumstances determine if the conversion
succeeds or if information is lost during the conversion. In the
following example, we define conversions between the musical
Note
type and a double
(which
represents the frequency in hertz of that note):
... // Convert to hertz public static implicit operator double(Note x) { return 440*Math.Pow(2,(double)x.value/12); } // Convert from hertz(only accurate to nearest semitone) public static explicit operator Note(double x) { return new Note((int)(0.5+12*(Math.Log(x/440)/Math.Log(2)))); } ... Note n =(Note)554.37; // explicit conversion double x = n; // implicit conversion
The
true
and
false
keywords are used as operators when defining
types with three-state logic, to enable these
types to seamlessly work with constructs that
take Boolean expressions,
namely the if
, do
,
while
, for
, and
conditional (?:
) statements. The
System.Data.SQLTypes. SQLBoolean
struct provides
this functionality:
public struct SQLBoolean ... { ... public static bool operator true(SQLBoolean x) { return x.value == 1; } public static bool operator false(SQLBoolean x) { return x.value == -1; } public static SQLBoolean operator !(SQLBoolean x) { return new SQLBoolean(- x.value); } public bool IsNull { get { return value == 0;} } ... } class Test { void Foo(SQLBoolean a) { if (a) Console.WriteLine("True"); else if (! a) Console.WriteLine("False"); else Console.WriteLine("Null"); } }
The
&&
and
||
operators are automatically evaluated from
&
and |
so they don’t
need to be overloaded. The []
operators can be
customized with indexers (see Section 2.9.6). The
assignment operator =
can’t be overloaded,
but all other assignment operators are automatically evaluated from
their corresponding binary
operators (e.g., +=
is evaluated from +
).
attributes
?
access
-
modifier
?
|
unsafe?
|
class-name
(
parameter-list
)
|
[ :[
base
|
this
]
(
argument-list
)
]?
|
statement-block
|
An instance of a constructor allows you to specify the code to be executed when a class or struct is instantiated. A class constructor first creates a new instance of that class on the heap and then performs initialization, while a struct constructor merely performs initialization.
Unlike ordinary methods, a constructor has the same name as the class or struct in which it is declared and has no return type:
class MyClass { public MyClass( ) { // initialization code } }
A class or struct can overload constructors and may call one of its
overloaded constructors before executing its method body using the
this
keyword:
class MyClass { public int x; public MyClass( ) : this(5) {} public MyClass(int v) { x = v; } } MyClass m1 = new MyClass( ); MyClass m2 = new MyClass(10); Console.WriteLine(m1.x) // 5 Console.Writeline(m2.x) // 10;
If a class does not define any constructors, an implicit parameterless constructor is created. A struct can’t define a parameterless constructor, since a constructor that initializes each field with a default value (effectively zero) is always implicitly defined.
A class constructor must call one of its base class constructors first. In the case where the base class has a parameterless constructor, that constructor is called implicitly. In the case where the base class provides only constructors that require parameters, the derived class constructor must explicitly call one of the base class constructors, using the base keyword. A constructor may also call an overloaded constructor (which calls base for it):
class B { public int x ; public B(int a) { x = a; } public B(int a, int b) { x = a * b; } // Notice how all of B's constructors need parameters } class D : B { public D( ) : this(7) {} // call an overloaded constructor public D(int a) : base(a) {} // call a base class constructor }
Another useful way to perform initialization is to assign fields an initial value in their declaration:
class MyClass { int x = 5; }
Field assignments are performed before the constructor is executed and are initialized in the textual order in which they appear. For classes, every field assignment in each class in the inheritance chain is executed before any of the constructors is executed, from the least derived to the most derived class.
attributes
?
|
static
class-name ( ) |
statement-block
|
A static constructor allows initialization code to be executed before the first instance of a class or struct is created, or before any static member of the class or struct is accessed. A class or struct can only define one static constructor, and it must be parameterless and have the same name as the class or struct:
class Test { static Test( ) { Console.WriteLine("Test Initialized"); } }
Consistent with instance constructors, static constructors respect the inheritance chain, so each static constructor from the least derived to the most derived is called.
Consistent with instance fields, each static field assignment is made before any of the static constructors is called, and the fields are initialized in the textual order in which they appear:
class Test { public static int x = 5; public static void Foo( ) {} static Test( ) { Console.WriteLine("Test Initialized"); } }
Accessing either Test.x
or
Test.Foo
assigns 5
to
x
, and prints Test
Initialized
.
Static constructors can’t be called
explicitly, and the runtime may invoke them well before they are
first used. Programs shouldn’t make any assumptions about the
timing of a static constructor’s invocation. In the following
example, Test
Initialized
can
be printed after Test2
Initialized
:
class Test2 { public static void Foo( ) {} static Test( ) { Console.WriteLine("Test2 Initialized"); } } Test.Foo( ); Test2.Foo( );
C# provides the keywords for accessing the members of a class itself
or of the class from which it is derived, namely the
this
and base
keywords.
The this
keyword denotes a variable that is a
reference to a class or struct instance, that is accessible only from
within nonstatic function members of the class or struct. The
this
keyword is also used by a constructor to call
an overloaded constructor (see Section 2.9.9) or declare or access indexers
(see Section 2.9.6). A
common use of the this
variable is to unambiguate
a field name from a parameter name:
class Dude { string name; public Test(string name) { this.name = name; } public void Introduce(Dude a) { if (a!=this) Console.WriteLine("Hello, I'm "+name); } }
The
base
keyword is
similar to the this
keyword, except that it
accesses an overridden or hidden base-class function member. The
base
keyword can also call a base-class
constructor (see Section 2.9.9) or access a base-class indexer
(using base
instead of this
).
Calling base
accesses the next most derived class
that defines that member. To build upon the example with the
this
keyword:
class Hermit : Dude { public void new Introduce(Dude a) { base.TalkTo(a); Console.WriteLine("Nice Talking To You"); } }
attributes
?
|
~
class-name
( )
|
statement-block
|
C# classes can declare destructors. Declaring a C# destructor is
simply a syntactic shortcut for declaring a
Finalize
method (known as a finalizer), and is
expanded by the compiler into the following method declaration:
protected override void Finalize( ) { ... base.Finalize( ); }
Although C# finalizers/destructors appear syntactically similar to C++ destructors, they have vastly different semantics, and it is critical to always be aware of the fact that a C# finalizer/destructor doesn’t behave the same as a C++ destructor.
For this reason we recommend that rather than declaring a C#
destructor and relying on the compiler to generate a finalizer under
the covers, you explicitly declare a Finalize
method when needed.
Finalizers are class-only methods intended to assist in the cleanup of non-memory resources and are generally called by the garbage collector just before reclaiming unused memory.
For more details on the garbage collector and finalizers, see Section 3.12 in Chapter 3.
A nested type is one declared within the scope of another type. Nesting a type has three benefits:
It can access all the members of its enclosing type, regardless of a member’s access modifier.
It can be hidden from other types with type-member access modifiers.
Accessing a nested type from outside its enclosing type requires specifying the type name (same principle as static members).
Here’s an example of a nested type:
using System; class A { int x = 3; // private member protected internal class Nested {// choose any access-level public void Foo ( ) { A a = new A ( ); Console.WriteLine (a.x); //can access A's private members } } } class B { static void Main ( ) { A.Nested n = new A.Nested ( ); // Nested is scoped to A n.Foo ( ); } } // an example of using "new" on a type declaration class C : A { new public class Nested {} // hide inherited type member }
18.191.176.194