Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C# Bible - Jeff Ferguson, Brian Patterson, Jason Beres

.pdf
Скачиваний:
64
Добавлен:
24.05.2014
Размер:
4.21 Mб
Скачать

Structures." The listing defines a class called Listing7_6. The class contains the definition of a structure called Point. This Point structure is defined within the Listing7_6 class, and it can be used as a type, just as the class uses any of the types built in to C#.

Using the this Keyword as an Identifier

With C#, you can use the this keyword to identify an object whose code is being executed, which in turn enables you to reference the current object.

You can use the this keyword in a variety of ways. You've already seen how it is used in an indexer. You can also use it as a prefix to a variable identifier to tell the C# compiler that an expression should reference a class field.

For example, consider the Point class in Listing 9-8.

Listing 9-8: Fields and Parameters with the Same Name

class Point

{

public int X; public int Y;

Point(int X, int Y)

{

X = X;

Y = Y;

}

public static void Main()

{

}

}

This code won't behave as expected because the X and Y identifiers are used twice: once as class field identifiers and once in the constructor's parameter list. The code needs to distinguish the field identifier X from the parameter list identifier X. With ambiguous code like this, the C# compiler assumes that the references to X and Y in the constructor statements refer to the parameters, and the code just sets the parameters to the value that they already contain.

You can use the this keyword to differentiate the field identifier from the parameter identifier. Listing 9-9 shows the corrected code with the this keyword used as the prefix for the field name.

Listing 9-9: Using this with Fields

class Point

{

public int X; public int Y;

Point(int X, int Y)

{

this.X = X; this.Y = Y;

}

public static void Main()

{

}

}

Understanding the Static Modifier

When you define a field or a method on a class, each object of that class created by code has its own copy of field values and methods. By using the static keyword, you can override this behavior, which enables multiple objects of the same class to share field values and methods.

Using static fields

As an example, let's return to our Point class. A Point class may have two fields for the point's x and y coordinates. Every object created from the Point class has copies of those fields, but each object can have its own values for the x and y coordinates. Setting the x and y coordinates of one object does not affect the settings of another object:

Point MyFirstPoint = new Point(100, 200);

Point MySecondPoint = new Point(150, 250);

In this example, two objects of class Point are created. The first object sets its copy of the x and y coordinates to 100 and 200, respectively, while the second object sets its copy of the x and y coordinates to 150 and 250, respectively. Each object is keeping its own copy of x and y coordinates.

Placing the static modifier before a field definition indicates that all objects of the same class will be sharing the same value. If one object sets a static value, all other objects of that same class will share that same value. Take a look at Listing 9-10.

Listing 9-10: Static Fields

class Point

{

public static int XCoordinate; public static int YCoordinate;

public int X

{

get

{

return XCoordinate;

}

}

public int Y

{

get

{

return YCoordinate;

}

}

public static void Main()

{

Point MyPoint = new Point();

System.Console.WriteLine("Before");

System.Console.WriteLine("======");

System.Console.WriteLine(MyPoint.X);

System.Console.WriteLine(MyPoint.Y);

Point.XCoordinate = 100;

Point.YCoordinate = 200;

System.Console.WriteLine("After");

System.Console.WriteLine("=====");

System.Console.WriteLine(MyPoint.X);

System.Console.WriteLine(MyPoint.Y);

}

}

The Point class in Listing 9-10 maintains two static integer fields called XCoordinate and YCoordinate. It also maintains two read-only properties, called X and Y, that return the values of the static variables.

The Main() method creates a new Point object and outputs its coordinates to the console. It then changes the values of the static fields and outputs the coordinates of the Point object a second time. The results are shown in Figure 9-3.

Figure 9-3: The usage of static fields simplifies coding.

Note that the values of the Point object coordinates have changed, although the values in the object itself were not changed. This is because the Point object shares static fields with all other Point objects, and when the Point class's static fields change, all the objects in that class reflect the change.

Also note the syntax for working with static fields. Static fields used in expressions are prefixed not with an object identifier, but with the name of the class that holds the static fields. The following statement is in error because MyPoint refers to an object, and XCoordinate refers to a static field:

MyPoint.XCoordinate = 100;

Writing code like this causes an error to be raised from the C# compiler:

error CS0176: Static member 'Point.XCoordinate' cannot be accessed with an instance reference; use typename instead

The static field must be prefixed with the name of the class:

Point.XCoordinate = 100;

Using static constants

Constants work just as fields do unless they are prefixed by the static keyword; if so, each object of the class maintains its own copy of the constant. However, making each object in a class maintain its own copy of a constant is a waste of memory.

Suppose you're writing a class called Circle, which manages a circle. Because you're working with a circle, you'll probably be using the value pi quite a bit. You wisely decide to make pi a constant, so you can refer to it in your code with a name, rather than a long floating-point number every time.

Now, what happens if you create one thousand circle objects? By default, they each get their own copy of the pi constant. You'll have one thousand copies of pi sitting in memory. This is a waste of memory, especially because pi is a constant and its value never changes. It makes more sense for every object in your Circle class to use a single copy of the pi constant.

This is where the static keyword comes in. Using the static keyword with a constant ensures that each object of a class works with a single in-memory copy of the constant's value:

const double Pi = 3.1415926535897932384626433832795;

In general, try to make all of your constants static constants so that only one copy of the constant's value is in memory at any one time.

Using static methods

When you first took a look at the Main() method, recall that it needed to be defined with the static keyword. Methods that are defined with the static keyword are called static methods. Methods that are not defined with the static keyword are called instance methods.

Static methods are listed in a class but do not belong to a specific object. Like static fields and static constants, all objects of a class share one copy of a static method. Static methods cannot refer to any part of an object that is not also marked as static, as shown in Listing 9-11.

Listing 9-11: Static Methods Calling Class Instance Methods

class Listing9_9

{

public static void Main()

{

CallMethod();

}

void CallMethod()

{

System.Console.WriteLine("Hello from CallMethod()");

}

}

The preceding code doesn't compile, and the C# compiler issues the following error:

error CS0120: An object reference is required for the nonstatic field, method, or property 'Listing9_9.CallMethod()'

The problem with the code in Listing 9-11 is that a static method, Main(), is trying to call an instance method, CallMethod(). This is forbidden because instance methods are part of an object instance, and static methods are not.

To correct this code, the static Main() method must create another object of the class and call the instance method from the new object, as shown in Listing 9-12.

Listing 9-12: Static Methods Calling Object Instance Methods

class Listing9_10

{

public static void Main()

{

Listing9_10 MyObject = new Listing9_10();

MyObject.CallMethod();

}

void CallMethod()

{

System.Console.WriteLine("Hello from CallMethod()");

}

}

The output from Listing 9-12 is shown in Figure 9-4.

Figure 9-4: Demonstrating a static method call from within the same class

Like all static class items, static methods appear only once in memory, which is why you must mark the Main() method as static. When .NET code is loaded into memory, the CLR starts by executing the Main() method. Remember that only one Main() method can be in memory at any one time. If a class had multiple Main() methods, the CLR would not know which Main() method to execute when the code needs to run. Using the static keyword on the Main() method ensures that only one copy of the Main() method is available in memory.

Note By using command-line parameters with the C# compiler, it is possible to include more than one Main() method within your application. This can be very handy when you want to try more than one method for debugging purposes.

Summary

C# is an object-oriented language, and the concepts that are used in object-oriented languages apply to C#. C# classes can make use of several types of class members:

Constants give a name to a value that doesn't change throughout the code. Using constants makes your code more readable because you can use the names of the constants in place of hardcoded literal values in your code.

Fields maintain the state of your classes. Fields are variables that are associated with an object.

Methods maintain the behavior of your classes. Methods are named pieces of code that perform a particular action for your class.

Properties enable you to expose your class's state to callers. Callers access properties with the same object.identifier syntax used to access fields. The advantage of properties over fields is that you can write code that is executed when property values are retrieved or set. This enables you to write validation code against new values assigned to properties, or to dynamically calculate the value of a property being retrieved. You can implement read-write properties, read-only properties, or writeonly properties.

Events enable your class to notify callers when certain actions take place within it. Callers can subscribe to class events and can receive notifications when the events actually occur.

Indexers enable your class to be accessed as if it were an array. Callers can use the square bracket array element syntax to execute your class's indexer accessor code. Use indexers when your class contains a collection of values and it makes sense to think of your class as an array of items.

Your class can redefine operators, as you see in Chapter 10. The operators can help determine how the class behaves when it is used in an expression with an operator.

Constructors are special methods that execute when objects of the class are created. You may define more than one constructor, each with a different parameter list. You can also define a class without a constructor. In this case, the C# compiler generates a default constructor that simply initializes all the fields in the object to a zero value.

Destructors are special methods that execute when objects of the class are destroyed. A class can have only one destructor. Because of the interaction with .NET code and the CLR, destructors execute when an object is garbage collected, not simply when the object's identifier is no longer accessible by code.

Classes can define types of their own, and these types can contain structure definitions and even definitions of other classes. After these types are defined, the class can use them just as it would use the types built into C#.

The this keyword refers to the current instance of an object. It is used as a prefix to differentiate a field identifier from a parameter identifier with the same name.

The static keyword tells the C# compiler that only one copy of a field or method is shared by all objects of the class. By default, each field and method in a C# class maintains its own copy of field values. Class items that do not use the static keyword are called instance methods.

Class items that do use the static keyword are called static methods.

Chapter 10: Overloading Operators

In This Chapter

Chapter 4 looked at the variety of operators available for use with expressions in C#. The C# language defines the behavior of these operators when used in an expression with the C# built-in data types. For example, C# defines the behavior of the addition operator as adding the values of two operands and providing the sum as the value of the expression.

With C#, you can define the behavior of many of the standard operators for use with your own structures and classes. You write special methods that define the behavior of your class when it appears in an expression using a C# operator. This enables your classes to be used in expressions that seem natural for other pieces of code to write. Suppose, for example, that you're writing a class that manages a set of records read from a database. If another piece of code has two objects of your class, it may want to write an expression that joins the records together into a third object. This sounds like an addition operation, and it seems natural for other pieces of code to write code like the following:

Records Records1;

Records Records2;

Records Records3;

Records3 = Records1 + Records2;

Your Records class would include a method that specifies how objects of the class would behave when used in an expression with the addition operator.

These methods are called user-defined operator implementations, and the object-oriented terminology for defining operator behavior in a class is called operator overloading. The word "overloading" is used because your body of code overloads the meaning of the same operator and makes it behave differently, depending on the context in which the operator is used.

All operator overloading methods must be declared with both the static and public keywords.

Overloadable Unary Operators

C# enables you to overload the behavior of the following unary operators in your classes and structures:

Unary plus

Unary minus

Logical negation

Bitwise complement operator

Prefix increment

Prefix decrement

The true keyword

The false keyword

Overloading unary plus

If you need to overload the unary plus, unary minus, negation, or bitwise complement operators in your class or structure, define a method with the following characteristics:

A return type of your choice

The keyword operator

The operator being overloaded

A parameter list specifying a single parameter of the type of class or structure containing the overloaded operator method

As an example, revisit the Point class from Chapter 9. Suppose that you want to add a unary plus operator to the class that, when used, ensures that the coordinates of the point are both positive. This is implemented in Listing 10-1.

Listing 10-1: Overloading the Unary Plus Operator

class Point

{

public int X; public int Y;

public static Point operator + (Point RValue)

{

Point NewPoint = new Point();

if(RValue.X < 0)

NewPoint.X = -(RValue.X); else

NewPoint.X = RValue.X;

if(RValue.Y < 0)

NewPoint.Y = -(RValue.Y); else

NewPoint.Y = RValue.Y;

return NewPoint;

}

public static void Main()

{

Point MyPoint = new Point();

MyPoint.X = -100;

MyPoint.Y = 200;

System.Console.WriteLine(MyPoint.X);

System.Console.WriteLine(MyPoint.Y);

MyPoint = +MyPoint; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y);

}

}

The Main() method creates an object of type Point and sets its initial coordinates to (100, 200). It then applies the unary plus operator to the object and reassigns the result to the same point. It prints out the x and y coordinates to the console.

The output of Listing 10-1 is shown in Figure 10-1.

Figure 10-1: Overloading the unary operator

The coordinates of the point have changed from (-100, 200) to (100, 200). The overloaded operator code is executed when the following statement is reached:

MyPoint = +MyPoint;

When this statement is reached, the unary plus operator overload for the Point class is executed. The expression on the right side of the equal sign is supplied as the parameter to the method.

Note The expression on the right-hand side of an assignment operator is often referred to as an rvalue, which is short for "right value." The expression on the left-hand side of an assignment operator is often referred to an lvalue, which is short for "left value."

Naming the parameter in the operator overload method RValue makes it clear that the rvalue of the assignment is being passed in. This is just a naming convention and not a requirement. You are free to name your parameters using any legal identifier allowed by C#.

This method creates a new Point object and then examines the coordinates of the supplied rvalue. If either of the parameters is negative, their values are negated, thereby turning them to positive values; and the now-positive values are assigned to the new point. Values that are not negative are assigned to the new point without any conversion. The new point is then returned from the method. The return value from the operator is used as the lvalue for the original statement.

The return type of operator overloads for the unary plus, unary minus, negation, or bitwise complement operators does not have to be the same type as the rvalue. It can be any C# type that makes the most sense for the operator.

Overloading unary minus

Much like the unary plus, you can perform the unary minus override in the very same fashion. Listing 10-2 overrides the minus operator to handle the Point class.

Listing 10-2: Overloading Unary Minus

class Point

{

public int X; public int Y;

public static Point operator - (Point RValue)

{

Point NewPoint = new Point();

if (RValue.X > 0)

NewPoint.X = -(RValue.X);

else

NewPoint.X = RValue.X;

if (RValue.Y > 0)

NewPoint.Y = -(RValue.Y);

else

NewPoint.Y = RValue.Y;

return NewPoint;

}

public static void Main()

{

Point MyPoint = new Point(); MyPoint.X = -100;

MyPoint.Y = 200; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y);

MyPoint = -MyPoint; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y);

}

}

After you define your new Point operator, you simply define the action it should take when presented with a variable of type Point. Listing 10-2 declares the x coordinate as -100 and the y coordinate as 200. You print these values out to the console for visual verification and then use your overloaded operator.

After your sample application has subtracted from the Point class, the resulting values are printed to the console window to indicate that it behaved as expected. Figure 10-2 is the output from Listing 10-2.

Соседние файлы в предмете Программирование