[ Team LiB ] |
3.1 Classesattributes? unsafe? access-modifier? new? [ abstract | sealed ]? class class-name [: base-class | : interface+ | : base-class, interface+ ]? { class-members } In C#, a program is built by defining new types, each with a set of data members and function members. Custom types should form higher-level building blocks that are easy to use, and closely model your problem space. In this example, we simulate an astronaut jumping on different planets, using three classes, Planet, Astronaut, and Test, to test our simulation. First, let's define the Planet class. By convention, we define the data members of the class at the top of the class declaration. There are two data members here: the name and gravity fields, which store the name and gravity of a planet. We then define a constructor for the planet. Constructors are function members that allow you to initialize an instance of your class. We initialize the data members with values fed to the parameters of the constructor. Finally, we define two more function members, which are properties that allow us to get the "Name" and "Gravity" of a planet. The Planet class looks like this: using System; class Planet { string name; // field double gravity; // field // constructor public Planet (string n, double g) { name = n; gravity = g; } // property public string Name { get {return name;} } // property public double Gravity { get {return gravity;} } } Next, we define the Astronaut class. As with the Planet class, we first define our data members. Here an astronaut has two fields: the astronaut's fitness, and the current planet the astronaut is on. We then provide a constructor, which initializes the fitness of an astronaut. Next we define a CurrentPlanet property that allows us to get or set the planet an astronaut is on. Finally we define a jump method that outputs how far the astronaut jumps, based on the fitness of the astronaut and the planet he is on. using System; class Astronaut { double fitness; // field Planet currentPlanet; // field // constructor public Astronaut (double f) { fitness = f; } // property public Planet CurrentPlanet { get { return currentPlanet; } set { currentPlanet = value; } } // method public void Jump ( ) { if (currentPlanet = = null) Console.WriteLine ("Bye Bye!"); else { double distance = fitness/currentPlanet.Gravity; Console.WriteLine ("Jumped {0} metres on {1}", distance, currentPlanet.Name); } } } Last, we define the Test class, which uses the Planet and Astronaut classes. Here we create two planets, earth and moon, and one astronaut, forestGump. Then we see how far forestGump jumps on each of these planets: class Test { static void Main ( ) { // create a new instance of a planet Planet earth = new Planet ("earth", 9.8); // create another new instance of a planet Planet moon = new Planet ("moon", 1.6); // create a new instance of an astronaut Astronaut forestGump = new Astronaut (20); forestGump.CurrentPlanet = earth; forestGump.Jump( ); forestGump.CurrentPlanet = moon; forestGump.Jump( ); } } // output Jumped 2.04 metres on earth Jumped 12.50 metres on moon If a class is designed well, it becomes a new higher-level building block that is easy for someone else to use. The user of a class seldom cares about the data members or implementation details of another class, merely its specification. To use a planet or an astronaut, all you need to know is how to use their public function members. In the following section, we look at each kind of type members a class can have, namely fields, constants, properties, indexers, methods, operators, constructors, destructors, and nested types. (Operators and events are explained in Chapter 4.) 3.1.1 The this KeywordThe this keyword denotes a variable that is a reference to a class or struct instance and is only accessible from within nonstatic function members of the class or struct. The this keyword is also used by a constructor to call an overloaded constructor (explained later in this chapter) or declare or access indexers (also explained later in this chapter). A common use of the this variable is to distinguish a field name from a parameter name. class Dude { string name; public Dude (string name) { this.name = name; } public void Introduce(Dude a) { if (a!=this) Console.WriteLine("Hello, I'm "+name); } } 3.1.2 Fieldsattributes? unsafe? access-modifier? new? static? [readonly | volatile]? type [ field-name [ = expr]? ]+ ; Fields hold data for a class or struct: class MyClass { int x; float y = 1, z = 2; static readonly int MaxSize = 10; ... } 3.1.2.1 Nonstatic fieldsNonstatic fields are also referred to as instance variables or instance data members. Static variables are also referred to as static variables or static data members. 3.1.2.2 The readonly modifierAs the name suggests, the readonly modifier prevents a field from being modified after it has been assigned. Such a field is termed a read-only field. A read-only field is always evaluated at runtime, not at compile time. A read-only field must be assigned in its declaration or within the type's constructor in order to compile (see more on constructors, later in this chapter), while non-read-only fields merely generate a warning when left unassigned. 3.1.3 Constantsattributes? access-modifier? new? const type [constant-name = constant-expr]+; A constant is a field that is evaluated at compile time and is implicitly static. The logical consequence of this is that a constant may not defer evaluation to a method or constructor, and may only be one of a few built-in types. These types are sbyte, byte, short, ushort, int, uint, long, ulong, float, double, decimal, bool, char, string, and enum. For example: public const double PI = 3.14159265358979323846; The benefit of a constant is that it is evaluated at compile time, permitting additional optimization by the compiler. 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 read-only field would not make this optimization, but is more versionable. For instance, suppose there was a mistake in calculation of pi, and Microsoft releases a patch to their library that contains the Math class, which is deployed to each client computer. If your software that uses the Circumference method is already deployed on a client machine, then the mistake is not fixed until you recompile your application with the latest version of the Math class. With a read-only field, however, this mistake is automatically fixed. Generally, this scenario occurs when a field value changes not as a result of a mistake, but simply because of an upgrade (such as MaxThreads changing from 500 to 1,000). 3.1.4 Propertiesattributes? unsafe? access-modifier? [ [[sealed | abstract]? override] | new? [virtual | abstract | static]? ]? type property-name { [ attributes? get // read-only statement-block | attributes? set // write-only statement-block | attributes? get // read-write statement-block attributes? set statement-block ] } Properties can be characterized as object-oriented fields. 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 named value that is of the property's type. A property can be read-only if it specifies only a get method, and write-only if it specifies only a write method (though rarely desirable). Many languages loosely implement properties with a get/set method convention, and C# properties are in fact compiled to get_XXX/set_XXX methods. This is the representation in MSIL: public int get_Cents {...} public void set_Cents (int value) {...}
3.1.5 Indexersattributes? unsafe? access-modifier? [ [[sealed | abstract]? override] | new? [virtual | abstract | static]? ]? type this [ attributes? [type arg]+ ] { attributes? get // read-only statement-block | attributes? set // write-only statement-block | attributes? get // read-write statement-block attributes? set statement-block } Indexers provide a natural way of indexing elements in a class or struct that encapsulate a collection, via an array's [ ] syntax. Indexers are similar to properties, but are accessed via an index, as opposed to a property name. The index can be any number of parameters. In the following example, the ScoreList class can be used to maintain the list of scores given by five judges. The indexer uses a single int index to get or set a particular judge's score. 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 Test { 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. Our example could be extended to return the score by a judge's name, as opposed to a numeric index. Indexers are compiled to get_Item (...)/set_Item (...) methods, which is the representation in MSIL. public Story get_Item (int index) {...} public void set_Item (int index, Story value) {...} 3.1.6 Methodsattributes? unsafe? access-modifier? [ [[sealed | abstract]? override] | new? [virtual | abstract | static extern?]? ]? [ void | type ] method-name (parameter-list) statement-blockParameter list syntax: [ attributes? [ref | out]? type arg ]* [ params attributes? type[ ] arg ]? All C# code executes in a method or in 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. 3.1.6.1 SignaturesA 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 by value. These characteristics are referred to as a method signature, because they uniquely distinguish one method from another. 3.1.6.2 Overloading methodsA type may overload methods (have multiple methods with the same name), as long as the signatures are different.[1] For example, the following methods can all coexist in the same type:
void Foo(int x); void Foo(double x); void Foo(int x, float y); void Foo(float x, int y); void Foo(ref int x); However, the following pairs of methods cannot coexist in the same type, since the return type and params modifier do not 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 3.1.7 Instance Constructorsattributes? unsafe? access-modifier? class-name (parameter-list) [ :[ base | this ] (argument-list) ]? statement-block Constructors allow initialization code to execute for a class or struct. 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 and has no return type: class MyClass { public MyClass( ) { // initialization code } } A class or struct may 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 parameter-free constructor is created. A struct cannot define a parameter-free constructor, since a constructor that initializes each field with a default value (effectively zero) is always implicitly defined. 3.1.7.1 Field initialization orderAnother 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. 3.1.7.2 Constructor access modifiersA class or struct may choose any access modifier for a constructor. It is occasionally useful to specify a private constructor to prevent a class from being constructed. This is appropriate for utility classes made up entirely of static members, such as the System.Math class. 3.1.8 Static Constructorsattributes? unsafe? extern? static class-name ( ) statement-block A static constructor allows initialization code to execute 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 define only one static constructor, and it must be parameter-free and have the same name as the class or struct: class Test { static Test( ) { Console.WriteLine("Test Initialized"); } } 3.1.8.1 Static field initialization orderEach static field assignment is made before any of the static constructors are called, and are initialized in the textual order in which they appear, which is consistent with instance fields. 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 then prints Test Initialized. 3.1.8.2 Nondeterminism of static constructorsStatic constructors cannot be called explicitly, and the runtime may invoke them well before they are first used. Programs should not make any assumptions about the timing of a static constructor's invocation. In this example, Test Initialized may be printed after Test2 Initialized: class Test2 { public static void Foo( ) { } static Test2 ( ) { Console.WriteLine("Test2 Initialized"); } } Test.Foo( ); Test2.Foo( ); 3.1.9 Destructors and Finalizersattributes? unsafe? ~class-name ( ) statement-block Destructors are class-only methods that are used to clean up nonmemory resources just before the garbage collector reclaims the memory for an object. Just as a constructor is called when an object is created, a destructor is called when an object is destroyed. C# destructors are very different from C++ destructors, primarily because of the presence of the garbage collector. First, memory is automatically reclaimed with a garbage collector, so a destructor in C# is used solely for nonmemory resources. Second, destructor calls are nondeterministic. The garbage collector calls an object's destructor when it determines it is no longer referenced; however, it may determine this after an undefined period of time has passed after the last reference to the object disappeared. A destructor is actually 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( ); } For more details on the garbage collector and finalizers, see Chapter 18. 3.1.10 Nested TypesA nested type is declared within the scope of another type. Nesting a type has three benefits:
For example: 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 }
|
[ Team LiB ] |