3.1 Classes

attributes? 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 Keyword

The 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 Fields

attributes? 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 fields

Nonstatic 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 modifier

As 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 Constants

attributes? 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 Properties

attributes? 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) {...}

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 in which a method call is replaced with the body of that method.

3.1.5 Indexers

attributes? 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 Methods

attributes? 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 Signatures

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 by value. These characteristics are referred to as a method signature, because they uniquely distinguish one method from another.

3.1.6.2 Overloading methods

A 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:

[1] An exception to this rule is that two otherwise identical signatures cannot coexist if one parameter has the ref modifier and the other parameter has the out modifier.

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 Constructors

attributes? 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 order

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.

3.1.7.2 Constructor access modifiers

A 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 Constructors

attributes? 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 order

Each 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 constructors

Static 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 Finalizers

attributes? 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 Types

A nested type is declared within the scope of another type. Nesting a type has three benefits:

  • A nested type can access all the members of its enclosing type, regardless of a member's access modifier.

  • A nested type can be hidden from other types with type-member access modifiers.

  • Accessing a nested type from outside of its enclosing type requires specifying the type name. This is the same principle used for static members.

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
}

Nested classes in C# are roughly equivalent to static inner classes in Java. There is no C# equivalent to Java's nonstatic inner classes; there is no "outer this" in C#.



    Part II: Programming with the .NET Framework
    Part IV: API Quick Reference
     
    ASPTreeView.com
     
    Evaluation has ДЖ»№ФВexpired.
    Info...