Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Teach Yourself The CSharp Language In 21 Days (2004) [eng].pdf
Скачиваний:
43
Добавлен:
16.08.2013
Размер:
8.5 Mб
Скачать
ANALYSIS

Tapping into OOP: Interfaces

429

LISTING 12.2 continued

25: } 26:

27:public int Sides

28:{

29:get { return InSides; }

30:set { InSides = value; }

31:}

32:

33:public Square()

34:{

35:Sides = 4;

36:}

37:}

38:

39:public class Props

40:{

41:public static void Main()

42:{

43:Square mySquare = new Square();

44:mySquare.SideLength = 5;

45:

46:Console.WriteLine(“\nDisplaying Square information:”);

47:Console.WriteLine(“Area: {0}”, mySquare.Area());

48:Console.WriteLine(“Sides: {0}”, mySquare.Sides);

49:}

50:}

Displaying Square information:

12

OUTPUT Area: 25

Sides: 4

This listing focuses on the use of a property rather than all the other code in the previous listing. You can see that the number of sides for the shape is now

accessed via a property instead of a method. In Lines 8–12, the IShape interface has a declaration for a property named Sides that will be used with an integer. This will have both the get and set methods. You should note that you are not required to specify both here; it would be perfectly acceptable to specify just a get or just a set. If both are specified in the interface, all classes that implement the interface must implement both.

Note

In the IShape interface used here, it would make sense to specify only the get property for Sides. Many shapes have a specific number of sides that could be set in the constructor and then never changed. The get method could still be used. If set were not included in the interface, a class could still implement set.

430

Day 12

The IShape interface is implemented in a Square class starting in Line 17. In Lines 27–31, the actual definitions for the get and set properties are defined. The code for the Square class’s implementation is straightforward. The Sides property sets the InSides data member.

Using a property that has been implemented via an interface is no different than using any other property. You can see the use of the Sides property in the previous listing in a number of lines. This includes getting the value in Line 48. The value is set in line 35 of the constructor.

Note

Many people will say that a class inherits from an interface. In a way, this is true; however, it is more correct to say that a class implements an interface.

Using Multiple Interfaces

One of the benefits of implementing interfaces instead of inheriting from a class is that you can implement more than one interface at a time. This gives you the power to do multiple inheritance without some of the downside.

To implement multiple interfaces, you separate each interface with a comma. To include both an IShape and an IShapeDisplay interface in a Square class, you use the following:

class Square : IShape, IShapeDisplay

{

...

}

You then need to implement all the constructs within both interfaces. Listing 12.3 illustrates the use of multiple interfaces.

LISTING 12.3 Multi.cs—Implementing Multiple Interfaces in a Single Class

1:// Multi.cs -

2://--------------------------------------------------------------------

4: using System; 5:

6:public interface IShape

7:{

8:// Cut out other methods to simplify example.

9:double Area();

10:int Sides { get; }

11:}

Tapping into OOP: Interfaces

LISTING 12.3 continued

12:

13:public interface IShapeDisplay

14:{

15:void Display();

16:}

17:

18:public class Square : IShape, IShapeDisplay

19:{

20:private int InSides;

21:public int SideLength;

22:

23:public int Sides

24:{

25:get { return InSides; }

26:}

27:

28:public double Area()

29:{

30:return ((double) (SideLength * SideLength));

31:}

32:

33:public double Circumference()

34:{

35:return ((double) (Sides * SideLength));

36:}

37:

38:public Square()

39:{

40:InSides = 4;

41:}

42:

43:public void Display()

44:{

45:Console.WriteLine(“\nDisplaying Square information:”);

46:Console.WriteLine(“Side length: {0}”, this.SideLength);

47:Console.WriteLine(“Sides: {0}”, this.Sides);

48:Console.WriteLine(“Area: {0}”, this.Area());

49:}

50:}

51:

52:public class Multi

53:{

54:public static void Main()

55:{

56:Square mySquare = new Square();

57:mySquare.SideLength = 7;

58:

59:mySquare.Display();

60:}

61:}

431

12

OUTPUT
ANALYSIS

432

Day 12

Displaying Square information:

Side length: 7

Sides: 4

Area: 49

You can see that two interfaces are declared and used in this listing. In Line 18, you can see that the Square class will implement the two interfaces. Because both

are included, all members of both interfaces must be implemented by the Square class. In looking at the code in Lines 23–49, you see that all the members are implemented.

Using Explicit Interface Members

So far, everything has gone smoothly with implementing interfaces. What happens, however, when you implement an interface that has a member name that clashes with another name already in use? For example, what would happen if the two interfaces in List-

ing 12.3 both had a Display method?

If a class includes two or more interfaces with the same member name, that member needs to be implemented only once. This single implementation of the method satisfies both interfaces.

Sometimes you want to implement the method independently for both interfaces. In this case, you need to use explicit interface implementations. An explicit implementation is done by including the interface name with the member name when you define the member. You must also use casting to call the method, as shown in Listing 12.4.

LISTING 12.4 Explicit.cs

1:// Explicit.cs -

2://--------------------------------------------------------------------

4: using System; 5:

6:public interface IShape

7:{

8:double Area();

9:int Sides { get; }

10:void Display();

11:}

12:

13:public interface IShapeDisplay

14:{

15:void Display();

16:}

17:

Tapping into OOP: Interfaces

LISTING 12.4 continued

18:public class Square : IShape, IShapeDisplay

19:{

20:private int InSides;

21:public int SideLength;

22:

23:public int Sides

24:{

25:get { return InSides; }

26:}

27:

28:public double Area()

29:{

30:return ((double) (SideLength * SideLength));

31:}

32:

33:public double Circumference()

34:{

35:return ((double) (Sides * SideLength));

36:}

37:

38:public Square()

39:{

40:InSides = 4;

41:}

42:

43:void IShape.Display()

44:{

45:Console.WriteLine(“\nDisplaying Square Shape\’s information:”);

46:Console.WriteLine(“Side length: {0}”, this.SideLength);

47:Console.WriteLine(“Sides: {0}”, this.Sides);

48:Console.WriteLine(“Area: {0}”, this.Area());

49:}

50:void IShapeDisplay.Display()

51:{

52:Console.WriteLine(“\nThis method could draw the shape...”);

53:}

54: 55: } 56:

57:public class Explicit

58:{

59:public static void Main()

60:{

61:Square mySquare = new Square();

62:mySquare.SideLength = 7;

63:

64:IShape ish = (IShape) mySquare;

65:IShapeDisplay ishd = (IShapeDisplay) mySquare;

433

12

OUTPUT
ANALYSIS

434

Day 12

LISTING 12.4 continued

67:ish.Display();

68:ishd.Display();

69:}

70:}

Displaying Square Shape’s information:

Side length: 7

Sides: 4

Area: 49

This method could draw the shape...

This listing is a bit more complicated, but the result is that you can explicitly declare and then use methods from different interfaces with the same name

within a single class.

This listing has two methods named Display. Each of these is explicitly defined within the Square class. You can see in Lines 43 and 50 that the explicit definitions use the explicit name of the method. The explicit name is the interface name and the member name separated by a period.

Using these explicit interfaces requires more work than calling the method. After all, if you call the method using the standard class name, which Display method would be used? To use one of the methods, you must cast the class to the interface type. In this case, it is a matter of casting the class to either IShape or IShapeDisplay. In Line 64, a variable, ish, is declared that is of type IShape. This is assigned to the mySquare class. A cast makes sure that the mySquare class is treated as an IShape type.

In Line 65, the myShape class is cast to a variable, ishd, that is of type IShapeDisplay. You can see in Lines 67–68 that these variables of interface types can then be used to call the appropriate Display method.

The end result of this listing is that you can have multiple interfaces with similarly named methods. Using explicit definitions and a little casting, you can make sure that the correct method is called. Why might you do this? For the previous listing, the IShapeDisplay interface might be used with shapes to ensure that all the classes have the capability of doing a graphical display method. The Display method in the IShape

might have the purpose of providing detailed textual information. By implementing both, you have the capability to get both types of display.

Tapping into OOP: Interfaces

435

Deriving New Interfaces from Existing Ones

As with classes, an interface can be derived from another interface. This inheritance of interfaces is done in a similar manner to inheriting classes. The following snippet shows how the IShape interface created earlier could be extended:

public interface IShape

{

long Area();

long Circumference(); int Sides{ get; set; };

}

interface I3DShape : IShape

{

int Depth { get; set; }

}

The I3DShape contains all the members of the IShape class and any new members that it adds. In this case, a Depth property member is added. You can then use the I3DShape interface as you would any other interface. Its members would be Area, Circumference, Sides,

and Depth.

Hiding Interface Members

It is possible to implement an interface member and yet hide its access from the base

 

class. This can be done to meet the requirement of implementing the interface and to

 

 

avoid cluttering up your class with additional members.

12

To hide an interface, you explicitly define it in the class. Listing 12.5 provides an exam-

 

ple of hiding an interface member.

 

 

 

LISTING 12.5 Hide.cs—Hiding an Interface Member from a Class

1:// Hide.cs -

2://--------------------------------------------------------------------

4: using System; 5:

6:public interface IShape

7:{

8:// members left out to simplify example...

9:int ShapeShifter( int val );

10:int Sides { get; set; }

11:}

12:

436

Day 12

LISTING 12.5 continued

13:public class Shape : IShape

14:{

15:private int InSides;

16:

17:public int Sides

18:{

19:get { return InSides; }

20:set { InSides = value; }

21:}

22:

23:int IShape.ShapeShifter( int val )

24:{

25:Console.WriteLine(“Shifting Shape....”);

26:val += 1;

27:return val;

28:}

29:

30:public Shape()

31:{

32:Sides = 5;

33:}

34:}

35:

36:public class Hide

37:{

38:public static void Main()

39:{

40:Shape myShape = new Shape();

42:Console.WriteLine(“My shape has been created.”);

43:Console.WriteLine(“Using get accessor. Sides = {0}”, myShape.Sides);

45:

//

myShape.Sides = myShape.ShapeShifter(myShape.Sides);

// error

46:

 

 

 

47:IShape tmp = (IShape) myShape;

48:myShape.Sides = tmp.ShapeShifter( myShape.Sides);

50:Console.WriteLine(“ShapeShifter called. Sides = {0}”, myShape.Sides);

51:}

52:}

My shape has been created.

OUTPUT Using get accessor. Sides = 5

Shifting Shape....

ShapeShifter called. Sides = 6

Tapping into OOP: Interfaces

437

ANALYSIS

This listing uses a scaled-down version of the IShape interface that you’ve seen

 

 

used throughout today’s lessons. The focus of this listing is to illustrate the point

 

 

 

of hiding an interface’s member from a class. In this case, the ShapeShifter method is

 

hidden from the Shape class. Line 45, which is commented out, is an attempt to use the

 

ShapeShifter method as a member of the Shape class. If you remove the comments from

 

the beginning of this line and try to compile and run this program, you get the following

 

error:

 

 

Hide2.cs(45,23): error CS0117: ‘Shape’ does not contain a definition for

 

 

‘ShapeShifter’

 

As you can see, Shape objects can’t directly access the ShapeShifter method—it is hidden

 

from them.

 

How is this done? When defining the interface member, you need to do it explicitly. In

 

Line 23, you see that the definition of the ShapeShifter method includes such an explicit

 

definition. The name of the interface is explicitly included in this line.

 

When calling the explicitly defined member, you need to do what was done in

 

Lines 47–48: You need to declare a variable of the interface type and then use a cast of

 

the interface type using the object that you want to access. In Line 47, you see that a

 

variable named tmp is created that is of the interface type IShape. The myShape object is

 

then cast to this variable using the same interface type. In Line 48, you see that this vari-

 

able of the interface type (IShape) can be used to get to the ShapeShifter method. The

 

output from Line 50 proves that the method was appropriately called.

 

The tmp variable can access the method because it is of the same type as the explicit dec-

12

laration in Line 23.

Summary

In today’s lesson you learned about interfaces, a construct that enables you to define what must be implemented. Interfaces can be used to ensure that different classes have similar implementations within them. You learned a number of things about interfaces, including how to use them, how to extend them, and how to implement—yet hide—some of their members from the base classes.

Q&A

Q Is it important to understand interfaces?

AYes. You will find interfaces used through C# programming. Many of the preconstructed methods provided within the class libraries include the use of interfaces.

438

Day 12

QYou said that the as and is keywords can be used with interfaces, yet you did not show an example. How does the use of these keywords differ from what was shown with classes?

AThe use of as and is with interfaces is nearly identical to their use with classes. Because the use is so similar, the coding examples with interfaces would be virtually the same as what was shown on Day 11.

Workshop

The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you’ve learned. Try to understand the quiz and exercise answers before continuing to tomorrow’s lesson. Answers are provided on the CD.

Quiz

1.Are interfaces a reference type or a value type?

2.What is the purpose of an interface?

3.Are members of an interface declared as public, private, or protected?

4.What is the primary difference between an interface and a class?

5.What inheritance is available with structures?

6.What types can be included in an interface?

7.How would you declare a public class named AClass that inherits from a class named baseClass and implements an interface named IMyInterface?

8.How many classes can be inherited at one time?

9.How many interfaces can be inherited (implemented) at one time?

10.How is an explicit interface implementation done?

Exercises

1.Write the code for an interface named Iid that has a property named ID as its only member.

2.Write the code that would declare an interface named IPosition. This interface should contain a method that takes a Point value and returns a Boolean.

3.Bug Buster: The following code snippet might have a problem. If so, what is it?

public interface IDimensions

{

long Width;

Tapping into OOP: Interfaces

439

long Height; double Area();

double Circumference(); int Sides();

}

4. Implement the IShape interface declared in Listing 12.1 into a class named

Rectangle.

12

WEEK 2

DAY 13

Making Your Programs React with Delegates, Events, and Indexers

You’ve learned many of the foundational topics related to C# programming. In today’s lesson, you learn about several additional topics that are foundational for your full understanding of C#. Today you…

Learn about indexers.

Build your own indexers.

Explore delegates.

Discover event programming.

Create your own events and event handlers.

Learn to multicast.

NEW TERM

442

Day 13

Using an Indexer

On Day 7, “Storing Complex Stuff: Structures, Enumerators, and Arrays,” you learned about arrays. Today you learn about indexers. An indexer enables you to

use an index on an object to obtain values stored within the object. In essence, this enables you to treat an object like an array.

An indexer is also similar to a property. As with properties, you use get and set when defining an indexer. Unlike properties, you are not obtaining a specific data member; instead, you are obtaining a value from the object itself. When you define a property, you define a property name. With indexers, instead of creating a name as you do with properties, you use the this keyword, which refers to the object instance and, thus, the object name is used. The format for defining an indexer is shown here:

public dataType this[int index]

{

get

{

// Do whatever you want...

return aValue;

}

set

{

//Do whatever you want

//Generally you should set a value within the class

//based on the index and the value they assign.

}

}

Creating an indexer enables you to use bracket notation ([]) with an object to set and get a value from an object. As you can see in the format shown earlier, you state the dataType that will be set and returned by the indexer. In the get section, you return a value that is of dataType. In the set block, you can do something with a value of dataType.

As with properties and member functions, you can use the value keyword. This is the value passed as the argument to the set routine. The best way to understand all of this is to take a look at a simple example of using an indexer, as shown in Listing 13.1.

LISTING 13.1 Indexer.cs—Using an Indexer

1:// Indexer.cs - Using an indexer

2://--------------------------------------------------------------------

4: using System; 5:

6: public class SpellingList

Making Your Programs React with Delegates, Events, and Indexers

443

LISTING 13.1 continued

7:{

8:protected string[] words = new string[size];

9:static public int size = 10;

10:

11:public SpellingList()

12:{

13:for (int x = 0; x < size; x++ )

14:words[x] = String.Format(“Word{0}”, x);

15:}

16:

17:public string this[int index]

18:{

19:get

20:{

21:string tmp;

22:

23:if( index >= 0 && index <= size-1 )

24:

tmp = words[index];

25:else

26:

tmp = “”;

27:

 

28:return ( tmp );

29:}

30:set

31:{

32:if( index >= 0 && index <= size-1 )

33: words[index] = value;

34:}

35:}

36:}

37:

38:public class Indexer

39:{

40:public static void Main()

41:{

42:SpellingList myList = new SpellingList();

43:

13

 

44:myList[3] = “=====”;

45:myList[4] = “Brad”;

46:myList[5] = “was”;

47:myList[6] = “Here!”;

48:myList[7] = “=====”;

50:for ( int x = 0; x < SpellingList.size; x++ )

51:Console.WriteLine(myList[x]);

52:}

53:}

OUTPUT
ANALYSIS

444

Day 13

Word0

Word1

Word2

=====

Brad was Here!

=====

Word8

Word9

This listing creates an indexer to be used with the SpellingList class. This class contains an array of strings named words that can be used to store a list of words.

This list is set to the size of the variable declared in Line 9.

Lines 11–15 contain a constructor for SpellingList that sets initial values into each element of the array. You could just as easily have requested the words from the reader or read them from a file. This constructor assigns the string value Word## to each of the elements in the array, where ## is the element number of the array.

Jumping down to the Indexer class in Lines 38–53, you see how the SpellingList class will be used. In Line 42, the SpellingList class is used to instantiate the myList object that will hold the words. Line 42 also causes the constructor to be executed. This initializes the Word## values. Lines 44–48 then change some of these values.

If you think back to how you worked with arrays, you should be saying, “wait a minute” as you look at Lines 44–48. To access the value of one of the words, you would normally have to access the data member within the object. When using arrays as a data member, you learned that you would assign a value to the fourth element as follows:

MyList.words[3] = “=====”;

Line 44, however, is accessing the fourth element within the object, which has been set to be the fourth element in the words array.

An indexer has been created for the SpellingList class in Lines 17–35. This indexer enables you to access the elements within the words array using just the object name.

Line 17 is the defining line for the indexer. You know that this is an indexer rather than a property because the this keyword is used instead of a name. Additionally, this is given an index (named index). The indexer will return a string value.

Lines 19–29 contain the get portion of the indexer. The get block returns a value based on the index. In this class, this value is an element from the words array. You can return any value that you want, but the value should make sense. In Line 23, a check is done to make sure that the index value is valid. If you don’t check the value of the index, you risk

Making Your Programs React with Delegates, Events, and Indexers

445

having an exception thrown. In this listing, if the index is out of the range, a null value is returned (in Line 26). If the index is valid, the value stored in the words array at the index location will be returned.

The set portion of the indexer is in Lines 30–34. This block can be used to set information within the object. As with properties, the value keyword contains the value being assigned. In this code, the index value is again checked to make sure it is valid. If it is, a word in the words array is updated at the index location with the value assigned.

Looking again at the test class, you see that the set indexer block is used to assign values in Lines 44–48. For Line 44, the set indexer block will be called with value equal to ===== and will be passed with an index value of 3. In Line 45, value is Brad and the index is 4. In Line 51, the get indexer block is called with an index value of x. The value returned will be the string value returned by the get indexer block.

Tip

You should use indexers when it makes your code more readable and easier to understand. For example, you can create a stack that can have items placed on it and taken off of it. Using an indexer, you could access items within the stack.

Exploring Delegates

NEW TERM

You now will learn about a more advanced topic: delegates. A delegate is a refer-

 

ence type that defines the signature for a method call. The delegate can then

 

 

 

 

 

accept and execute methods that are of the same format as this signature.

 

You learned in yesterday’s lesson that an interface is a reference type that defines the

 

layout for a class, but it does not itself define any of the functionality. Delegates are

 

often compared to interfaces. A delegate defines the layout for a method but does not

 

actually define a method. Instead, a delegate can accept and work with methods that

13

match its layout (signature).

An example will help make delegates clearer. In this example, a program will be created that sorts two numbers, either ascending or descending. The sorting direction is determined in the code presented; however, you could ask the reader to enter the direction of the sort. Based on the direction—ascending or descending—a different method will be used. Despite this, only one call, to a delegate, will be made. The delegate will be given the appropriate method to execute.

The format for declaring a delegate is shown here:

public delegate returnType DelegateName(parameters);

446

Day 13

Here, public can be replaced with appropriate access modifiers, and delegate is the keyword used to indicate that this is a delegate. The rest of the definition is for the signature of the method that the delegate will work with. As you should know from previous lessons, the signature includes the data type to be returned by the method (returnType), as well as the name of the parameters that will be received by the method (parameters). The name of the delegate goes where the method name would normally go. Because the delegate will be used to execute multiple methods that fit the return type and parameters, you don’t know the specific method names.

The example used here creates a delegate named Sort that can take multiple sorting methods. These methods will not return a value, so their return type will be void. The methods that will be used for sorting will pass in two integer variables that will be reference types. This enables the sorting functions to switch the values, if necessary. The delegate definition for the example is as follows:

public delegate void Sort(ref int a, ref int b);

Notice the semicolon at the end. Although this looks similar to a method definition, it is not a method definition. There is no body—a delegate is just a template for methods that can be executed. Methods are actually applied, or delegated, to this delegate to be executed.

A delegate is a template for multiple methods. For example, the Sort delegate in the example can be used with methods that don’t return a value and that take two reference integers as parameters. The following is an example of a method that can be used with the Sort delegate:

public static void Ascending( ref int first, ref int second )

{

if (first > second )

{

int tmp = first; first = second; second = tmp;

}

}

This method, Ascending, is of type void. Additionally, it receives two values that are both ref int. This matches the signature of the Sort delegate so that it can be used. This method takes the two values and checks to see whether the first is greater than the second. If it is, the two values are swapped using a simple sort routine. Because these values are ref types, the calling routine will have the values swapped as well.

Making Your Programs React with Delegates, Events, and Indexers

447

A second method named Descending can also be created:

public static void Descending( ref int first, ref int second )

{

if (first < second )

{

int tmp = first; first = second; second = tmp;

}

}

This method is similar to Ascending, except that the larger value is kept in the first position. You could declare additional sort routines to use with this delegate, as long as the signature of your methods matches. Additionally, different programs could use the Sort delegate and have their own logic within their methods.

Now that a delegate has been declared and multiple functions can be used with it, what is next?

You need to associate your methods with the delegate. Instantiating delegate objects can do this. A delegate object is declared like other objects, with the parameter for the initializer being the method name that you are assigning (delegating) to the delegate. For example, to declare a delegate that can be used with the Ascending method, you code the following:

Sort up = new Sort(Ascending);

This creates a delegate object named up that can then be used. up is associated with the Ascending method that was declared. The following creates a Sort delegate associated with the Descending method. This delegate is called down.

Sort down = new Sort(Descending);

Now you’ve declared your delegate, created methods that can be used with it, and associated these methods to delegate objects. How do you get the delegated methods to exe-

cute? You create a method that receives a delegate object as a parameter. This generic 13 method can then execute the method from the delegate:

public void DoSort(Sort ar)

{

ar(ref val1, ref val2);

}

As you can see, the DoSort method receives a delegate named ar as its parameter.

This method then executes ar. You should notice that ar has the same signature as your

448

Day 13

delegate. If your delegate has a return type, ar will have a return type. The method of the ar call also matches your delegate. In essence, the DoSort method executes whichever method is passed as a Sort delegate object. For our example, if up is passed, ar(...) is equivalent to calling the Ascending method. If down is passed, ar(...) is equivalent to calling the Descending method.

You’ve now seen all the key pieces to working with a delegate. Listing 13.2 pulls the entire sample together into a workable solution.

LISTING 13.2 SortClass.cs—Using a Simple Delegate

1:// SortClass.cs - Using a delegates

2://--------------------------------------------------------------------

4: using System; 5:

6:public class SortClass

7:{

8:static public int val1;

9:static public int val2;

11: public delegate void Sort(ref int a, ref int b); 12:

13:public void DoSort(Sort ar)

14:{

15:ar(ref val1, ref val2);

16:}

17:}

18:

19:public class SortProgram

20:{

21:public static void Ascending( ref int first, ref int second )

22:{

23:if (first > second )

24:{

25:int tmp = first;

26:first = second;

27:second = tmp;

28:}

29:}

30:

 

 

31:

public static void Descending( ref int first, ref int second )32:

{

33:if (first < second )

34:{

35:int tmp = first;

36:first = second;

37:second = tmp;

38:}