C H A P T E R  6

More About Classes

Class Members

The previous two chapters covered two of the nine types of class members: fields and methods. In this chapter, I’ll introduce all the other class members except events and operators, and explain their features. I’ll cover events in Chapter 14.

Table 6-1 shows a list of the class member types. Those that have already been introduced are marked with diamonds. Those that are covered in this chapter are marked with a check. Those that will be covered later in the text are marked with empty check boxes.

Image

Order of Member Modifiers

Previously, you saw that the declarations of fields and methods can include modifiers such as public and private. In this chapter, I’ll discuss a number of additional modifiers. Since many of these modifiers can be used together, the question that arises is, what order do they need to be in?

Class member declaration statements consist of the following: the core declaration, an optional set of modifiers, and an optional set of attributes. The syntax used to describe this structure is the following. The square brackets indicate that the enclosed set of components is optional.

   [ attributes ] [ modifiers ]  CoreDeclaration

The optional components are the following:

  • Modifiers
    • If there are any modifiers, they must be placed before the core declaration.
    • If there are multiple modifiers, they can be in any order.
  • Attributes
    • If there are any attributes, they must be placed before the modifiers and core declaration.
    • If there are multiple attributes, they can be in any order.

So far, I’ve explained only two modifiers: public and private. I’ll cover attributes in Chapter 24. For example, public and static are both modifiers that can be used together to modify certain declarations. Since they’re both modifiers, they can be placed in either order. The following two lines are semantically equivalent:

   public static int MaxVal;

   static public int MaxVal;

Figure 6-1 shows the order of the components as applied to the member types shown so far: fields and methods. Notice that the type of the field and the return type of the method are not modifiers—they’re part of the core declaration.

Image

Figure 6-1. The order of attributes, modifiers, and core declarations

Instance Class Members

Class members can be associated with an instance of the class or with the class as a whole; that is, to all the instances of the class. By default, members are associated with an instance. You can think of each instance of a class as having its own copy of each class member. These members are called instance members.

Changes to the value of one instance field do not affect the values of the members in any other instance. So far, the fields and methods you’ve seen have all been instance fields and instance methods.

For example, the following code declares a class, D, with a single integer field, Mem1. Main creates two instances of the class. Each instance has its own copy of field Mem1. Changing the value of one instance’s copy of the field doesn’t affect the value of the other instance’s copy. Figure 6-2 shows the two instances of class D.

   class D
   {
      public int Mem1;
   }
   
   class Program
   {
      static void Main()
      {
         D d1 = new D();
         D d2 = new D();
         d1.Mem1 = 10; d2.Mem1 = 28;
   
         Console.WriteLine("d1 = {0}, d2 = {1}", d1.Mem1, d2.Mem1);
      }
   }

This code produces the following output:


d1 = 10, d2 = 28

Image

Figure 6-2. Each instance of class D has its own copy of field Mem1.

Static Fields

Besides instance fields, classes can have what are called static fields.

  • A static field is shared by all the instances of the class, and all the instances access the same memory location. Hence, if the value of the memory location is changed by one instance, the change is visible to all the instances.
  • Use the static modifier to declare a field static, as follows:
   class D
   {
      int Mem1;                     // Instance field
      static int Mem2;              // Static field
        
   }  Keyword

For example, the code on the left in Figure 6-3 declares class D with static field Mem2 and instance field Mem1. Main defines two instances of class D. The figure shows that static field Mem2 is stored separately from the storage of any of the instances. The gray fields inside the instances represent the fact that, from inside an instance method, the syntax to access or update the static field is the same as for any other member field.

  • Because Mem2 is static, both instances of class D share a single Mem2 field. If Mem2 is changed, that change is seen from both.
  • Member Mem1 is not declared static, so each instance has its own distinct copy.
Image

Figure 6-3. Static and instance data members

Accessing Static Members from Outside the Class

In the previous chapter, you saw that dot-syntax notation is used to access public instance members from outside the class. Dot-syntax notation consists of listing the instance name, followed by a dot, followed by the member name.

Static members, like instance members, are also accessed from outside the class using dot-syntax notation. But since there is no instance, you must use the class name, as shown here:

   Class name
    
    D.Mem2 = 5;            // Accessing the static class member
       
     Member name

Example of a Static Field

The following code expands the preceding class D by adding two methods:

  • One method sets the values of the two data members.
  • The other method displays the values of the two data members.
   class D {
      int        Mem1;
      static int Mem2;

      public void SetVars(int v1, int v2) // Set the values
      {  Mem1 = v1; Mem2 = v2; }
                       Access as if it were an instance field

      public void Display( string str )
      {  Console.WriteLine("{0}: Mem1= {1}, Mem2= {2}", str, Mem1, Mem2); }
   }
                                                  Access as if it were an instance field
   class Program {
      static void Main()
      {
         D d1 = new D(), d2 = new D();   // Create two instances.

         d1.SetVars(2, 4);               // Set d1's values.
         d1.Display("d1");

         d2.SetVars(15, 17);             // Set d2's values.
         d2.Display("d2");

         d1.Display("d1");       // Display d1 again and notice that the
      }                          // value of static member Mem2 has changed!
   }

This code produces the following output:


d1: Mem1= 2, Mem2= 4
d2: Mem1= 15, Mem2= 17
d1: Mem1= 2, Mem2= 17

Lifetimes of Static Members

The lifetimes for static members are different from those of instance members.

  • As you saw previously, instance members come into existence when the instance is created and go out of existence when the instance is destroyed.
  • Static members, however, exist and are accessible even if there are no instances of the class.

Figure 6-4 illustrates a class, D, with a static field, Mem2. Even though Main doesn’t define any instances of the class, it assigns the value 5 to the static field and prints it out with no problem.

Image

Figure 6-4. Static fields with no class instances can still be assigned to and read from, because the field is associated with the class, not an instance.

The code in Figure 6-4 produces the following output:


Mem2 = 5

Image Note Static members exist even if there are no instances of the class. If a static field has an initializer, the field is initialized before the use of any of the class’s static fields, but not necessarily at the beginning of program execution.

Static Function Members

Besides static fields, there are also static function members.

  • Static function members, like static fields, are independent of any class instance. Even if there are no instances of a class, you can still call a static method.
  • Static function members cannot access instance members. They can, however, access other static members.

For example, the following class contains a static field and a static method. Notice that the body of the static method accesses the static field.

    class X
    {
       static public int A;                               // Static field
       static public void PrintValA()                     // Static method
       {
          Console.WriteLine("Value of A: {0}", A);
    }                                          
  }                                        Accessing the static field

The following code uses class X, defined in the preceding code:

   class Program
   {
      static void Main()
      {
         X.A = 10;               // Use dot-syntax notation
         X.PrintValA();          // Use dot-syntax notation
      }  
   }  Class name

This code produces the following output:


Value of A: 10

Figure 6-5 illustrates the preceding code.

Image

Figure 6-5. Static methods of a class can be called even if there are no instances of the class.

Other Static Class Member Types

The types of class members that can be declared static are shown checked in Table 6-2. The other member types cannot be declared static.

Image

Member Constants

Member constants are like the local constants covered in the previous chapter, except that they’re declared in the class declaration rather than in a method, as shown in the following example:

   class MyClass
   {
       const int IntVal = 100;              // Defines a constant of type int
                                         // with a value of 100.
   }         Type        Initializer
   
   const double PI = 3.1416;               // Error: cannot be declared outside a type
                                           // declaration

Like local constants, the value used to initialize a member constant must be computable at compile time and is usually one of the predefined simple types or an expression composed of them.

   class MyClass
   {
      const int IntVal1 = 100;
      const int IntVal2 = 2 * IntVal1;  // Fine, since the value of IntVal1
   }                                    // was set in the previous line.

Like local constants, you cannot assign to a member constant after its declaration.

   class MyClass
   {
      const int IntVal;                // Error: initialization is required.
      IntVal = 100;                    // Error: assignment is not allowed.
   }

Image Note Unlike C and C++, in C# there are no global constants. Every constant must be declared within a type.

Constants Are Like Statics

Member constants, however, are more interesting than local constants, in that they act like static values. They’re “visible” to every instance of the class, and they’re available even if there are no instances of the class. Unlike actual statics, constants do not have their own storage locations and are substituted in by the compiler at compile time in a manner similar to #define values in C and C++.

For example, the following code declares class X with constant field PI. Main doesn’t create any instances of X, and yet it can use field PI and print its value. Figure 6-6 illustrates the code.

   class X
   {
      public const double PI = 3.1416;
   }

   class Program
   {
      static void Main()
      {
         Console.WriteLine("pi = {0}", X.PI);    // Use static field PI
      }
   }

This code produces the following output:


pi = 3.1416

Image

Figure 6-6. Constant fields act like static fields but do not have a storage location in memory.

Although a constant member acts like a static, you cannot declare a constant as static, as shown in the following line of code.

   static const double PI = 3.14;     // Error: can't declare a constant as static

Properties

A property is a member that represents an item of data in a class or class instance. Using a property appears very much like writing to, or reading from, a field. The syntax is the same.

For example, the following code shows the use of a class called MyClass that has both a public field and a public property. From their usage, you cannot tell them apart.

   MyClass mc = new MyClass();
   
   mc.MyField    = 5;                               // Assigning to a field
   mc.MyProperty = 10;                              // Assigning to a property
   
   WriteLine("{0} {1}", mc.MyField, mc.MyProperty); // Read field and property

A property, like a field, has the following characteristics:

  • It is a named class member.
  • It has a type.
  • It can be assigned to and read from.

Unlike a field, however, a property is a function member, hence:

  • It does not necessarily allocate memory for data storage.
  • It executes code.

A property is a named set of two matching methods called accessors.

  • The set accessor is used for assigning a value to the property.
  • The get accessor is used for retrieving a value from the property.

Figure 6-7 shows the representation of a property. The code on the left shows the syntax of declaring a property named MyValue, of type int. The image on the right shows how properties will be represented visually in this text. Notice that the accessors are shown sticking out the back, because, as you will soon see, they’re not directly callable.

Image

Figure 6-7. An example property named MyValue, of type int

Property Declarations and Accessors

The set and get accessors have predefined syntax and semantics. You can think of the set accessor as a method with a single parameter that “sets” the value of the property. The get accessor has no parameters and returns the value of the property.

  • The set accessor always has the following:
    • A single, implicit value parameter named value, of the same type as the property
    • A return type of void
  • The get accessor always has the following:
    • No parameters
    • A return type of the same type as the property

Figure 6-8 shows the structure of a property declaration. Notice in the figure that neither accessor declaration has explicit parameter or return type declarations. They don’t need them, because they’re implicit in the type of the property.

Image

Figure 6-8. The syntax and structure of a property declaration

The implicit parameter value in the set accessor is a normal value parameter. Like other value parameters, you can use it to send data into a method body—or in this case, the accessor block. Once inside the block, you can use value like a normal variable, including assigning values to it.

Other important points about accessors are the following:

  • All paths through the implementation of a get accessor must include a return statement that returns a value of the property type.
  • The set and get accessors can be declared in either order, and no methods other than the two accessors are allowed on a property.

A Property Example

The following code shows an example of the declaration of a class called C1, which contains a property named MyValue.

  • Notice that the property itself doesn’t have any storage. Instead, the accessors determine what should be done with data sent in and what data should be sent out. In this case, the property uses a field called TheRealValue for storage.
  • The set accessor takes its input parameter, value, and assigns that value to field TheRealValue.
  • The get accessor just returns the value of field TheRealValue.

Figure 6-9 illustrates the code.

   class C1
   {
      private int TheRealValue;               // Field: memory allocated

      public int MyValue                      // Property: no memory allocated
      {
         set
         {
            TheRealValue = value;
         }

         get
         {
            return TheRealValue;
         }
      }
   }
Image

Figure 6-9. Property accessors often use fields for storage

Using a Property

As you saw previously, you write to and read from a property in the same way you access a field. The accessors are called implicitly.

  • To write to a property, use the property’s name on the left side of an assignment statement.
  • To read from a property, use the property’s name in an expression.

For example, the following code contains an outline of the declaration of a property named MyValue. You write to and read from the property using just the property name, as if it were a field name.

   int MyValue             // Property declaration
   {
      set{ ... }
      get{ ... }
   }
   ...
   Property name
      
   MyValue = 5;            // Assignment: the set method is implicitly called.
   z = MyValue;            // Expression: the get method is implicitly called.
          
   Property name

The appropriate accessor is called implicitly depending on whether you are writing to or reading from the property. You cannot explicitly call the accessors. Attempting to do so produces a compile error.

   y = MyValue.get();      // Error! Can't explicitly call get accessor.
   MyValue.set(5);         // Error! Can't explicitly call set accessor.

Properties and Associated Fields

A property is often associated with a field, as shown in the previous two sections. A common practice is to encapsulate a field in a class by declaring the field private and declaring a public property to give controlled access to the field from outside the class. The field associated with a property is called the backing field or backing store.

For example, the following code uses the public property MyValue to give controlled access to private field TheRealValue:

   class C1
   {
      private int TheRealValue = 10;   // Backing Field: memory allocated
      public  int MyValue              // Property: no memory allocated
      {
         set{ TheRealValue = value; }  // Sets the value of field TheRealValue
         get{ return TheRealValue; }   // Gets the value of the field
      }
   }

   class Program
   {
      static void Main()
   {
                                      Read from the property as if it were a field.
         C1 c = new C1();                       ↓    
         Console.WriteLine("MyValue:  {0}", c.MyValue);

         c.MyValue = 20;         Use assignment to set the value of a property.
         Console.WriteLine("MyValue:  {0}", c.MyValue);
      }
   }

There are several conventions for naming properties and their backing fields. One convention is to use the same string for both names but use camel casing for the field and Pascal casing for the property. (Camel casing describes a compound word identifier where the first letter of each word, except the first, is capitalized, and the rest of the letters are lowercase. Pascal casing is where the first letter of each word in the compound is capitalized.) Although this violates the general rule that it’s bad practice to have different identifiers that differ only in casing, it has the advantage of tying the two identifiers together in a meaningful way.

Another convention is to use Pascal casing for the property, and then for the field, use the camel case version of the same identifier, with an underscore in front.

The following code shows both conventions:

   private int firstField;                  // Camel casing
   public  int FirstField                   // Pascal casing
   {
      get { return firstField; }
      set { firstField = value; }
   }

   private int _secondField;                // Underscore and camel casing
   public  int SecondField
   {
      get { return _secondField; }
      set { _secondField = value; }
   }

Performing Other Calculations

Property accessors are not limited to just passing values back and forth from an associated backing field; the get and set accessors can perform any, or no, computations. The only action required is that the get accessor return a value of the property type.

For instance, the following example shows a valid (but probably useless) property that just returns the value 5 when its get accessor is called. When the set accessor is called, it doesn’t do anything. The value of implicit parameter value is ignored.

   public int Useless
   {
      set{  /* I'm not setting anything.                */ }
      get
      {     /* I'm always just returning the value 5.   */
         return 5;
      }
   }

The following code shows a more realistic and useful property, where the set accessor performs filtering before setting the associated field. The set accessor sets field TheRealValue to the input value—unless the input value is greater than 100. In that case, it sets TheRealValue to 100.

   int TheRealValue = 10;                     // The field
   int MyValue                                // The property
   {
      set                                     // Sets the value of the field
      {
         TheRealValue = value > 100           // but makes sure it's not > 100
                           ? 100
                           : value;
      }
      get                                     // Gets the value of the field
      {
         return TheRealValue;
      }
   }

Image Note In the preceding code sample, the syntax between the equal sign and the end of the statement might look somewhat strange. That expression uses the conditional operator, which I’ll cover in greater detail in Chapter 8. The conditional operator is a ternary operator that evaluates the expression in front of the question mark, and if the expression evaluates to true, it returns the expression after the question mark. Otherwise, it returns the expression after the colon. Some people would use an if...then statement here, but the conditional operator is more appropriate, as you’ll see when we look at both constructs in detail in Chapter 8.

Read-Only and Write-Only Properties

You can leave one or the other (but not both) of a property’s accessors undefined by omitting its declaration.

  • A property with only a get accessor is called a read-only property. A read-only property is a safe way of passing an item of data out from a class or class instance without allowing too much access.
  • A property with only a set accessor is called a write-only property. A write-only property is a safe way of passing an item of data from outside the class to the class without allowing too much access.
  • At least one of the two accessors must be defined, or the compiler will produce an error message.

Figure 6-10 illustrates read-only and write-only properties.

Image

Figure 6-10. A property can have one or the other of its accessors undefined.

Properties vs. Public Fields

As a matter of preferred coding practice, properties are preferred over public fields for several reasons:

  • Since properties are function members, as opposed to data members, they allow you to process the input and output, which you can’t do with public fields.
  • You can have read-only or write-only properties, but you can’t have these characteristics with a field.
  • The semantics of a compiled variable and a compiled property are different.

The second point has implications when you release an assembly that is accessed by other code. For example, sometimes you may be tempted to use a public field rather than a property, with the reasoning that if you ever need to add processing to the data held in the field, you can always change the field to a property at a later time. This is true, but if you make that change, you will also have to recompile any other assemblies accessing that field, because the compiled semantics of fields and properties are different. On the other hand, if you implement it as a property and just change its implementation, you won’t need to recompile the other assemblies accessing it.

An Example of a Computed, Read-Only Property

In most of the examples so far, the property has been associated with a backing field, and the get and set accessors have referenced that field. However, a property does not have to be associated with a field. In the following example, the get accessor computes the return value.

In the code, class RightTriangle represents, not surprisingly, a right triangle. Figure 6-11 illustrates read-only property Hypotenuse.

  • It has two public fields that represent the lengths of the two right-angle sides of the triangle. These fields can be written to and read from.
  • The third side is represented by property Hypotenuse, which is a read-only property whose return value is based on the lengths of the other two sides. It isn’t stored in a field. Instead, it computes the correct value, on demand, for the current values of A and B.
   class RightTriangle
   {
      public double A = 3;
      public double B = 4;
      public double Hypotenuse                    // Read-only property
      {
         get{ return Math.Sqrt((A*A)+(B*B)); }    // Calculate return value
      }
   }

   class Program
   {
      static void Main()
      {
         RightTriangle c = new RightTriangle();
         Console.WriteLine("Hypotenuse:  {0}", c.Hypotenuse);
      }
   }

This code produces the following output:


Hypotenuse:  5

Image

Figure 6-11. Read-only property Hypotenuse

Automatically Implemented Properties

Because properties are so often associated with backing fields, C# provides automatically implemented properties, or auto-implemented properties, which allow you to just declare the property, without declaring a backing field. The compiler creates a hidden backing field for you and automatically hooks up the get and set accessors to it.

The important points about auto-implemented properties are the following:

  • You do not declare the backing field—the compiler allocates the storage for you, based on the type of the property.
  • You cannot supply the bodies of the accessors—they must be declared simply as semicolons. The get acts as a simple read of the memory, and the set as a simple write.
  • You cannot access the backing field other than through the accessors. Since you can’t access it any other way, it wouldn’t make sense to have read-only or write-only auto-implemented properties—so they’re not allowed.

The following code shows an example of an automatically implemented property:

   class C1
   {                  ← No declared backing field
      public int MyValue                         // Allocates memory
      {
         set; get;
      }        
   }  The bodies of the accessors are declared as semicolons.

   class Program
   {
      static void Main()
      {                        Use auto-implemented properties as regular properties.
         C1 c = new C1();                         
         Console.WriteLine("MyValue:  {0}", c.MyValue);

         c.MyValue = 20;
         Console.WriteLine("MyValue:  {0}", c.MyValue);
      }
   }

This code produces the following output:


MyValue:  0
MyValue:  20

Besides being convenient, auto-implemented properties allow you to easily insert a property where you might be tempted to declare a public field.

Static Properties

Properties can also be declared static. Accessors of static properties, like all static members, have the following characteristics:

  • They cannot access instance members of a class—although they can be accessed by them
  • They exist regardless of whether there are instances of the class.
  • They must be referenced by the class name, rather than an instance name, when being accessed from outside the class.

For example, the following code shows a class with an auto-implemented static property called MyValue. In the first three lines of Main, the property is accessed, even though there are no instances of the class. The last line of Main calls an instance method that accesses the property from inside the class.

   class Trivial
   {
      public static int MyValue { get;  set; }
   
      public void PrintValue()               Accessed from inside the class
      {                                                  
         Console.WriteLine("Value from inside: {0}", MyValue);
      }
   }

   class Program
   {
      static void Main()                   Accessed from outside the class
      {                                              ↓       
         Console.WriteLine("Init Value: {0}", Trivial.MyValue);
         Trivial.MyValue = 10;           ←  Accessed from outside the class
         Console.WriteLine("New Value : {0}", Trivial.MyValue);

         Trivial tr = new Trivial();
         tr.PrintValue();
      }
   }

Init Value: 0
New Value : 10
Value from inside: 10

Instance Constructors

An instance constructor is a special method that is executed whenever a new instance of a class is created.

  • A constructor is used to initialize the state of the class instance.
  • If you want to be able to create instances of your class from outside the class, you need to declare the constructor public.

Figure 6-12 shows the syntax of a constructor. A constructor looks like the other methods in a class declaration, with the following exceptions:

  • The name of the constructor is the same as the name of the class.
  • A constructor cannot have a return value.
Image

Figure 6-12. Constructor declaration

For example, the following class uses its constructor to initialize its fields. In this case, it has a field called TimeOfInstantiation that is initialized with the current date and time.

   class MyClass
   {
      DateTime TimeOfInstantiation;                        // Field
      ...
      public MyClass()                                     // Constructor
      {
         TimeOfInstantiation = DateTime.Now;               // Initialize field
      }
      ...
   }

Image Note Having just finished the section on static properties, take a closer look at the line that initializes TimeOfInstantiation. The DateTime class (actually it’s a struct, but you can think of it as a class since I haven’t covered structs yet) is from the BCL, and Now is a static property of DateTime. The Now property creates a new instance of the DateTime class, initializes it with the current date and time from the system clock, and returns a reference to the new DateTime instance.

Constructors with Parameters

Constructors are like other methods in the following ways:

  • A constructor can have parameters. The syntax for the parameters is exactly the same as for other methods.
  • A constructor can be overloaded.

When you use an object-creation expression to create a new instance of a class, you use the new operator followed by one of the class’s constructors. The new operator uses that constructor to create the instance of the class.

For example, in the following code, Class1 has three constructors: one that takes no parameters, one that takes an int, and another that takes a string. Main creates an instance using each one.

   class Class1
   {
      int Id;
      string Name;

      public Class1()            { Id=28;    Name="Nemo"; }   // Constructor 0
      public Class1(int val)     { Id=val;   Name="Nemo"; }   // Constructor 1
      public Class1(String name) { Name=name;             }   // Constructor 2

      public void SoundOff()
      { Console.WriteLine("Name {0},   Id {1}", Name, Id); }
   }

   class Program
   {
      static void Main()
      {
         Class1 a = new Class1(),                     // Call constructor 0.
                b = new Class1(7),                    // Call constructor 1.
                c = new Class1("Bill");               // Call constructor 2.

         a.SoundOff();
         b.SoundOff();
         c.SoundOff();
      }
   }

This code produces the following output:


Name Nemo,   Id 28
Name Nemo,   Id 7
Name Bill,   Id 0

Default Constructors

If no instance constructor is explicitly supplied in the class declaration, then the compiler supplies an implicit, default constructor, which has the following characteristics:

  • It takes no parameters.
  • It has an empty body.

If you declare any constructors at all for a class, then the compiler does not define the default constructor for the class.

For example, Class2 in the following example declares two constructors.

  • Because there is at least one explicitly defined constructor, the compiler does not create any additional constructors.
  • In Main, there is an attempt to create a new instance using a constructor with no parameters. Since there is no constructor with zero parameters, the compiler produces an error message.
   class Class2
   {
      public Class2(int Value)    { ... }   // Constructor 0
      public Class2(String Value) { ... }   // Constructor 1
   }

   class Program
   {
      static void Main()
      {
         Class2 a = new Class2();   // Error! No constructor with 0 parameters
         ...
      }
   }

Image Note You can assign access modifiers to instance constructors just as you can to other members. You’ll also want to declare the constructors public so that you can create instances from outside the class. You can also create private constructors, which cannot be called from outside the class, but can be used from within the class, as you’ll see in the next chapter.

Static Constructors

Constructors can also be declared static. While an instance constructor initializes each new instance of a class, a static constructor initializes items at the class level. Generally, static constructors initialize the static fields of the class.

  • Class-level items are initialized
    • Before any static member is referenced
    • Before any instance of the class is created
  • Static constructors are like instance constructors in the following ways:
    • The name of the static constructor must be the same as the name of the class.
    • The constructor cannot return a value.
  • Static constructors are unlike instance constructors in the following ways:
    • Static constructors use the static keyword in the declaration.
    • There can only be a single static constructor for a class, and it cannot have parameters.
    • Static constructors cannot have accessibility modifiers.

The following is an example of a static constructor. Notice that its form is the same as that of an instance constructor, but with the addition of the static keyword.

   class Class1
   {
      static Class1 ()
      {
         ...                // Do all the static initializations.
      }
      ...

Other important things you should know about static constructors are the following:

  • A class can have both a static constructor and instance constructors.
  • Like static methods, a static constructor cannot access instance members of its class and cannot use the this accessor, which we’ll cover shortly.
  • You cannot explicitly call static constructors from your program. They’re called automatically by the system, at some time
    • Before any instance of the class is created
    • Before any static member of the class is referenced

Example of a Static Constructor

The following code uses a static constructor to initialize a private static field named RandomKey, of type Random. Random is a class provided by the BCL to produce random numbers. It’s in the System namespace.

   class RandomNumberClass
   {
      private static Random RandomKey;         // Private static field
   
      static RandomNumberClass()               // Static constructor
      {
         RandomKey = new Random();             // Initialize RandomKey
      }

      public int GetRandomNumber()
      {
         return RandomKey.Next();
      }
   }
   
   class Program
   {
      static void Main()
      {
         RandomNumberClass a = new RandomNumberClass();
         RandomNumberClass b = new RandomNumberClass();

         Console.WriteLine("Next Random #: {0}", a.GetRandomNumber());
         Console.WriteLine("Next Random #: {0}", b.GetRandomNumber());
      }
   }

One execution of this code produced the following output:


Next Random #: 47857058
Next Random #: 1124842041

Object Initializers

So far in the text, you’ve seen that an object-creation expression consists of the keyword new followed by a class constructor and its parameter list. An object initializer extends that syntax by placing a list of member initializations at the end of the expression. An object initializer allows you to set the values of fields and properties when creating a new instance of an object.

The syntax has two forms, as shown here. One form includes the constructor’s argument list, and the other doesn’t. Notice that the first form doesn’t even use the parentheses that would enclose the argument list.

                                                  Object initializer
                                                 ↓                            
   new TypeName          { FieldOrProp = InitExpr, FieldOrProp = InitExpr, ...}
   new TypeName(ArgList) { FieldOrProp = InitExpr, FieldOrProp = InitExpr, ...}
                                       ↑                      ↑
                                  Member initializer            Member initializer

For example, for a class named Point with two public integer fields X and Y, you could use the following expression to create a new object:

   new Point { X = 5, Y = 6 };
                       
                Init X    Init Y

Important things to know about object initializers are the following:

  • The fields and properties being initialized must be accessible to the code creating the object. For example, in the previous code, X and Y must be public.
  • The initialization occurs after the constructor has finished execution, so the values might have been set in the constructor and then reset to the same or a different value in the object initialize.

The following code shows an example of using an object initializer. In Main, pt1 calls just the constructor, which sets the values of its two fields. For pt2, however, the constructor sets the fields’ values to 1 and 2, and the initializer changes them to 5 and 6.

   public class Point
   {
      public int X = 1;
      public int Y = 2;
   }

   class Program
   {
      static void Main( )
      {                            Object initializer
         Point pt1 = new Point();              
         Point pt2 = new Point   { X = 5, Y = 6 };
         Console.WriteLine("pt1: {0}, {1}", pt1.X, pt1.Y);
         Console.WriteLine("pt2: {0}, {1}", pt2.X, pt2.Y);
      }
   }

This code produces the following output:


pt1: 1, 2
pt2: 5, 6

Destructors

Destructors perform actions required to clean up or release unmanaged resources after an instance of a class is no longer referenced. Unmanaged resources are such things as file handles that you’ve gotten using the Win32 API, or chunks of unmanaged memory. These aren’t things you’ll get by using .NET resources, so if you stick to the .NET classes, you won’t likely have to write destructors for your classes. For this reason, I’m going to save the description of destructors until Chapter 25.

The readonly Modifier

A field can be declared with the readonly modifier. The effect is similar to declaring a field as const, in that once the value is set, it cannot be changed.

  • While a const field can only be initialized in the field’s declaration statement, a readonly field can have its value set in any of the following places:
    • The field declaration statement—like a const.
    • Any of the class constructors. If it’s a static field, then it must be done in the static constructor.
  • While the value of a const field must be determinable at compile time, the value of a readonly field can be determined at run time. This additional freedom allows you to set different values under different circumstances or in different constructors!
  • Unlike a const, which always acts like a static, the following is true of a readonly field:
    • It can be either an instance field or a static field.
    • It has a storage location in memory.

For example, the following code declares a class called Shape, with two readonly fields.

  • Field PI is initialized in its declaration.
  • Field NumberOfSides is set to either 3 or 4, depending on which constructor is called.
   class Shape
   {  Keyword           Initialized
          ↓                     
      readonly double PI = 3.1416;
      readonly int    NumberOfSides;
          ↑                ↑
       Keyword            Not initialized

      public Shape(double side1, double side2)                  // Constructor
      {
         // Shape is a rectangle
         NumberOfSides = 4;
               
         ... Set in constructor
      }

      public Shape(double side1, double side2, double side3)    // Constructor
      {
         // Shape is a triangle
         NumberOfSides = 3;
                
         ... Set in constructor
      }
   }

The this Keyword

The this keyword, used in a class, is a reference to the current instance. It can be used only in the blocks of the following class members:

  • Instance constructors.
  • Instance methods.
  • Instance accessors of properties and indexers. (Indexers are covered in the next section.)

Clearly, since static members are not part of an instance, you cannot use the this keyword inside the code of any static function member. Rather, it is used for the following:

  • To distinguish between class members and local variables or parameters
  • As an actual parameter when calling a method

For example, the following code declares class MyClass, with an int field and a method that takes a single int parameter. The method compares the values of the parameter and the field and returns the greater value. The only complicating factor is that the names of the field and the formal parameter are the same: Var1. The two names are distinguished inside the method by using the this access keyword to reference the field.

   class MyClass {
      int Var1 = 10;
           ↑    Both are called “Var1”    ↓
      public int ReturnMaxSum(int Var1)
      {       Parameter     Field
                 ↓        ↓    
         return Var1 > this.Var1
                     ? Var1                 // Parameter
                     : this.Var1;           // Field
      }
   }

   class Program {
      static void Main()
      {
         MyClass mc = new MyClass();

         Console.WriteLine("Max: {0}", mc.ReturnMaxSum(30));
         Console.WriteLine("Max: {0}", mc.ReturnMaxSum(5));
      }
   }

This code produces the following output:


Max: 30
Max: 10

Indexers

Suppose you were to define class Employee, with three fields of type string (as shown in Figure 6-13). You could then access the fields using their names, as shown in the code in Main.

Image

Figure 6-13. Simple class without indexers

There are times, however, when it would be convenient to be able to access them with an index, as if the instance were an array of fields. This is exactly what indexers allow you to do. If you were to write an indexer for class Employee, method Main might look like the code in Figure 6-14. Notice that instead of using dot-syntax notation, indexers use index notation, which consists of an index between square brackets.

Image

Figure 6-14. Using indexed fields

What Is an Indexer?

An indexer is a pair of get and set accessors, similar to those of properties. Figure 6-15 shows the representation of an indexer for a class that can get and set values of type string.

Image

Figure 6-15. Representations of an indexer

Indexers and Properties

Indexers and properties are similar in many ways.

  • Like a property, an indexer does not allocate memory for storage.
  • Both indexers and properties are used primarily for giving access to other data members with which they’re associated and for which they provide get and set access.
    • A property usually represents a single data member.
    • An indexer usually represents multiple data members.

Image Note You can think of an indexer as a property that gives get and set access to multiple data members of the class. You select which of the many possible data members by supplying an index, which itself can be of any type—not just numeric.

Some additional points you should know about indexers are the following:

  • Like a property, an indexer can have either one or both of the accessors.
  • Indexers are always instance members; hence, an indexer cannot be declared static.
  • Like properties, the code implementing the get and set accessors does not have to be associated with any fields or properties. The code can do anything, or nothing, as long as the get accessor returns some value of the specified type.

Declaring an Indexer

The syntax for declaring an indexer is shown below. Notice the following about indexers:

  • An indexer does not have a name. In place of the name is the keyword this.
  • The parameter list is between square brackets.
  • There must be at least one parameter declaration in the parameter list.
             Keyword     Parameter list
                                     
   ReturnType this [ Type param1, ... ]
   {                                 
      get     Square bracket   Square bracket
      {
            ...
      }
      set
      {
            ...
      }
   }

Declaring an indexer is similar to declaring a property. Figure 6-16 shows the syntactic similarities and differences.

Image

Figure 6-16. Comparing an indexer declaration to a property declaration

The Indexer set Accessor

When the indexer is the target of an assignment, the set accessor is called and receives two items of data, as follows:

  • An implicit parameter, named value, which holds the data to be stored
  • One or more index parameters that represent where it should be stored
    emp[0] = "Doe";
              
       Index   Value
    Parameter

Your code in the set accessor must examine the index parameters, determine where the data should be stored, and then store it.

Figure 6-17 shows the syntax and meaning of the set accessor. The left side of the figure shows the actual syntax of the accessor declaration. The right side shows the semantics of the accessor if it were written using the syntax of a normal method. The figure on the right shows that the set accessor has the following semantics:

  • It has a void return type.
  • It uses the same parameter list as that in the indexer declaration.
  • It has an implicit value parameter named value, of the same type as the indexer.
Image

Figure 6-17. The syntax and meaning of the set accessor declaration

The Indexer get Accessor

When the indexer is used to retrieve a value, the get accessor is called with one or more index parameters. The index parameters represent which value to retrieve.

   string s = emp[0];
                  
              Index parameter

The code in the get accessor body must examine the index parameters, determine which field they represent, and return the value of that field.

Figure 6-18 shows the syntax and meaning of the get accessor. The left side of the figure shows the actual syntax of the accessor declaration. The right side shows the semantics of the accessor if it were written using the syntax of a normal method. The semantics of the get accessor are as follows:

  • It has the same parameter list as in the indexer declaration.
  • It returns a value of the same type as the indexer.
Image

Figure 6-18. The syntax and meaning of the get accessor declaration

More About Indexers

As with properties, the get and set accessors cannot be called explicitly. Instead, the get accessor is called automatically when the indexer is used in an expression for a value. The set accessor is called automatically when the indexer is assigned a value with the assignment statement.

When an indexer is “called,” the parameters are supplied between the square brackets.

     Index   Value
             
   emp[0] = "Doe";                               // Calls set accessor
   string NewName = emp[0];                      // Calls get accessor
                        
                      Index

Declaring the Indexer for the Employee Example

The following code declares an indexer for the earlier example: class Employee.

  • The indexer must read and write values of type string—so string must be declared as the indexer’s type. It must be declared public so that it can be accessed from outside the class.
  • The three fields in the example have been arbitrarily indexed as integers 0 through 2, so the formal parameter between the square brackets, named index in this case, must be of type int.
  • In the body of the set accessor, the code determines which field the index refers to and assigns the value of implicit variable value to it. In the body of the get accessor, the code determines which field the index refers to and returns that field’s value.
   class Employee
   {
      public string LastName;                     // Call this field 0.
      public string FirstName;                    // Call this field 1.
      public string CityOfBirth;                  // Call this field 2.

      public string this[int index]               // Indexer declaration
      {
         set                                      // Set accessor declaration
         {
            switch (index) {
               case 0: LastName = value;
                  break;
               case 1: FirstName = value;
                  break;
               case 2: CityOfBirth = value;
                  break;

               default:                           // (Exceptions in Ch. 11)
                  throw new ArgumentOutOfRangeException("index");
            }
         }

         get                                      // Get accessor declaration
         {
            switch (index) {
               case 0: return LastName;
               case 1: return FirstName;
               case 2: return CityOfBirth;

               default:                           // (Exceptions in Ch. 11)
                  throw new ArgumentOutOfRangeException("index");
            }
         }
      }
   }

Another Indexer Example

The following is an additional example that indexes the two int fields of class Class1:

   class Class1
   {
      int Temp0;                         // Private field
      int Temp1;                         // Private field
      public int this [ int index ]      // The indexer
      {
         get
         {
            return ( 0 == index )        // Return value of either Temp0 or Temp1
                        ? Temp0
                        : Temp1;
         }

         set
         {
            if( 0 == index )
               Temp0 = value;            // Note the implicit variable "value".
            else
               Temp1 = value;            // Note the implicit variable "value".
         }
      }
   }

   class Example
   {
      static void Main()
      {
         Class1 a = new Class1();

         Console.WriteLine("Values -- T0: {0},  T1: {1}", a[0], a[1]);
         a[0] = 15;
         a[1] = 20;
         Console.WriteLine("Values -- T0: {0}, T1: {1}", a[0], a[1]);
      }
   }

This code produces the following output:


Values -- T0: 0,  T1: 0
Values -- T0: 15, T1: 20

Indexer Overloading

A class can have any number of indexers, as long as the parameter lists are different; it isn’t sufficient for the indexer type to be different. This is called indexer overloading, because all the indexers have the same “name”—the this access reference.

For example, the following class has three indexers: two of type string and one of type int. Of the two indexers of type string, one has a single int parameter, and the other has two int parameters.

   class MyClass
   {
      public string this [ int index ]
      {
         get { ... }
         set { ... }
      }
   
      public string this [ int index1, int index2 ]
      {
         get { ... }
         set { ... }
      }
   
      public int this [ float index1 ]
      {
         get { ... }
         set { ... }
      }
   
      ...
   }

Image Note Remember that the overloaded indexers of a class must have different parameter lists.

Access Modifiers on Accessors

In this chapter, you’ve seen two types of function members that have get and set accessors: properties and indexers. By default, both a member’s accessors have the same access level as the member itself. That is, if a property has an access level of public, then both its accessors have that same access level. The same is true of indexers.

You can, however, assign different access levels to the two accessors. For example, the following code shows a common and important paradigm of declaring a private set accessor and a public get accessor. The get is public because the access level of the property is public.

Notice in this code that although the property can be read from outside the class, it can only be set from inside the class itself, in this case by the constructor. This is an important tool for encapsulation.

   class Person     Accessors with different access levels
   {                                     
      public string Name { get; private set; }
      public Person( string name )
      {
         Name = name;
      }
   }

   class Program
   {
      static public void Main( )
      {
         Person p = new Person( "Capt. Ernest Evans" );
         Console.WriteLine( "Person's name is {0}", p.Name );
      }
   }

This code produces the following output:


Person's name is Capt. Ernest Evans

There are several restrictions on the access modifiers of accessors. The most important ones are the following:

  • An accessor can have an access modifier only if the member (property or indexer) has both a get accessor and a set accessor.
  • Although both accessors must be present, only one of them can have an access modifier.
  • The access modifier of the accessor must be strictly more restrictive than the access level of the member.

Figure 6-19 shows the hierarchy of access levels. The access level of an accessor must be strictly lower in the chart than the access level of the member.

For example, if a property has an access level of public, you can give any of the four lower access levels on the chart to one of the accessors. But if the property has an access level of protected, the only access modifier you can use on one of the accessors is private.

Image

Figure 6-19. Hierarchy of strictly restrictive accessor levels

Partial Classes and Partial Types

The declaration of a class can be partitioned among several partial class declarations.

  • Each of the partial class declarations contains the declarations of some of the class members.
  • The partial class declarations of a class can be in the same file or in different files.

Each partial declaration must be labeled as partial class, in contrast to the single keyword class. The declaration of a partial class looks the same as the declaration of a normal class, other than the addition of the type modifier partial.

   Type modifier
        
     partial class MyPartClass    // Same class name as following
     {
        member1 declaration
        member2 declaration
           ...
     }

   Type modifier
        
     partial class MyPartClass    // Same class name as preceding
     {
        member3 declaration
        member4 declaration
           ...
     }

Image Note The type modifier partial is not a keyword, so in other contexts you can use it as an identifier in your program. But when used immediately before the keywords class, struct, or interface, it signals the use of a partial type.

For example, the box on the left of Figure 6-20 represents a file with a class declaration. The boxes on the right of the figure represent that same class declaration split into two files.

Image

Figure 6-20. Class split using partial types

All the partial class declarations comprising a class must be compiled together. A class using partial class declarations has the same meaning as if all the class members were declared within a single class declaration body.

Visual Studio uses this feature in its standard Windows program templates. When you create an ASP.NET project, a Windows Forms project, or a Windows Presentation Foundation (WPF) project from the standard templates, the templates create two class files for each web page, form, or WPF window. In the cases of ASP.NET or Windows Forms, the following is true:

  • One file contains the partial class containing the code generated by Visual Studio, declaring the components on the page. You shouldn’t modify the partial class in this file, since it’s regenerated by Visual Studio when you modify the components on the page.
  • The other file contains the partial class you use to implement the look and behavior of the components of the page or form.

Besides partial classes, you can also create two other partial types, which are the following:

  • Partial structs. (Structs are covered in Chapter 10.)
  • Partial interfaces. (Interfaces are covered in Chapter 15.)

Partial Methods

Partial methods are methods that are declared in different parts of a partial class. The different parts of the partial method can be declared in different parts of the partial class or in the same part. The two parts of the partial method are the following:

  • The defining partial method declaration
    • Lists the signature and return type.
    • The implementation part of the declaration syntax consists of only a semicolon.
  • The implementing partial method declaration
    • Lists the signature and return type.
    • The implementation is in the normal format, which, as you know, is a statement block.

The important things to know about partial methods are the following:

  • The defining and implementing declarations must match in signature and return type. The signature and return type have the following characteristics:
    • The return type must be void.
    • The signature cannot include access modifiers, making partial methods implicitly private.
    • The parameter list cannot contain out parameters.
    • The contextual keyword partial must be included in both the defining and implementing declarations immediately before the keyword void.
  • You can have a defining partial method without an implementing partial method. In this case, the compiler removes the declaration and any calls to the method made inside the class. You cannot have an implementing partial method without a defining partial method.

The following code shows an example of a partial method called PrintSum.

  • PrintSum is declared in different parts of partial class MyClass: the defining declaration is in the first part and the implementing declaration is in the second part. The implementation prints out the sum of its two integer parameters.
  • Since partial methods are implicitly private, PrintSum cannot be called from outside the class. Method Add is a public method that calls PrintSum.
  • Main creates an object of class MyClass and calls public method Add, which calls method PrintSum, which prints out the sum of the input parameters.
   partial class MyClass
   {        Must be void
                
      partial void PrintSum(int x, int y);      // Defining partial method
                                        
   Contextual keyword                   No implementation here
      public void Add(int x, int y)
      {
         PrintSum(x, y);
      }
   }

   partial class MyClass
   {
      partial void PrintSum(int x, int y)       // Implementing partial method
      {
         Console.WriteLine("Sum is {0}", x + y);       Implementation
      }
   }

   class Program
   {
      static void Main( )
      {
         var mc = new MyClass();
         mc.Add(5, 6);
      }
   }

This code produces the following output:


Sum is 11

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.249.42