- Class Members
- Order of Member Modifiers
- Instance Class Members
- Static Fields
- Static Function Members
- Other Static Class Member Types
- Member Constants
- Constants Are Like Statics
- Properties
- Instance Constructors
- Static Constructors
- Object Initializers
- Destructors
- The readonly Modifier
- The this Keyword
- Indexers
- Access Modifiers on Accessors
- Partial Classes and Partial Types
- Partial Methods
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.
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.
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
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 classD
share a singleMem2
field. IfMem2
is changed, that change is seen from both.- Member
Mem1
is not declaredstatic
, so each instance has its own distinct copy.
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
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
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.
The code in Figure 6-4 produces the following output:
Mem2 = 5
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.
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.
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
.
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.
}
Note Unlike C and C++, in C# there are no global constants. Every constant must be declared within a type.
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
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
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.
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.
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 areturn
statement that returns a value of the property type.- The
set
andget
accessors can be declared in either order, and no methods other than the two accessors are allowed on a property.
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 fieldTheRealValue
.- The
get
accessor just returns the value of fieldTheRealValue
.
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;
}
}
}
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.
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; }
}
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;
}
}
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.
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.
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.
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 ofA
andB
.
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
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 theset
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.
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
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.
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
}
...
}
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 struct
s 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 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
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
...
}
}
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.
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
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
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
andY
must bepublic
.- 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 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.
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, areadonly
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 thestatic
constructor.- While the value of a
const
field must be determinable at compile time, the value of areadonly
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 areadonly
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, 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
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
.
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.
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
.
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.
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
andset
accessors does not have to be associated with any fields or properties. The code can do anything, or nothing, as long as theget
accessor returns some value of the specified type.
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.
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.
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.
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
The following code declares an indexer for the earlier example: class Employee
.
- The indexer must read and write values of type
string
—sostring
must be declared as the indexer’s type. It must be declaredpublic
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 typeint
.- In the body of the
set
accessor, the code determines which field the index refers to and assigns the value of implicit variablevalue
to it. In the body of theget
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");
}
}
}
}
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
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 { ... }
}
...
}
Note Remember that the overloaded indexers of a class must have different parameter lists.
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 aset
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
.
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
...
}
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.
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 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 keywordvoid
.- 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 classMyClass
: 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. MethodAdd
is a public method that callsPrintSum
.Main
creates an object of classMyClass
and calls public methodAdd
, which calls methodPrintSum
, 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
3.142.249.42