Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 50 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
50
Dung lượng
186,53 KB
Nội dung
82 | Chapter 4: Classes and Objects Implementing the Close( ) Method For some objects, you may prefer to have your clients call a method named Close( ). (For example, Close( ) may make more sense than Dispose( ) for file objects.) You can implement this by creating a private Dispose( ) method and a public Close( ) method, and having your Close( ) method invoke Dispose( ). The using Statement To make it easier for your clients to properly dispose of your objects, C# provides a using statement that ensures that Dispose() will be called at the earliest possible time. The idiom is to declare the objects you are using and then to create a scope for these objects with curly braces. When the closing brace is reached, the Dispose( ) method will be called on the object automatically, as illustrated in Example 4-6. In the first part of this example, the Font object is created within the using statement. When the using statement ends, Dispose( ) is called on the Font object. Example 4-6. The using statement #region Using directives using System; using System.Collections.Generic; using System.Drawing; using System.Text; #endregion namespace usingStatement { class Tester { public static void Main( ) { using ( Font theFont = new Font( "Arial", 10.0f ) ) { // use theFont } // compiler will call Dispose on theFont Font anotherFont = new Font( "Courier", 12.0f ); using ( anotherFont ) { // use anotherFont } // compiler calls Dispose on anotherFont } } } Passing Parameters | 83 In the second part of the example, a Font object is created outside the using state- ment. When we decide to use that font, we put it inside the using statement; when that statement ends, Dispose( ) is called once again. This second approach is fraught with danger. If an exception is thrown after the object is created, but before the using block is begun, the object will not be dis- posed. Second, the variable remains in scope after the using block ends, but if it is accessed, it will fail. The using statement also protects you against unanticipated exceptions. Regardless of how control leaves the using statement, Dispose( ) is called. An implicit try- finally block is created for you. (See Chapter 11 for details.) Passing Parameters By default, value types are passed into methods by value. (See the section “Method Arguments,” earlier in this chapter.) This means that when a value object is passed to a method, a temporary copy of the object is created within that method. Once the method completes, the copy is discarded. Although passing by value is the normal case, there are times when you will want to pass value objects by reference. C# provides the ref parameter modifier for passing value objects into a method by refer- ence, and the out modifier for those cases in which you want to pass in a ref variable without first initializing it. C# also supports the params modifier, which allows a method to accept a variable number of parameters. I discuss the params keyword in Chapter 9. Passing by Reference Methods can return only a single value (though that value can be a collection of val- ues). Let’s return to the Time class and add a GetTime( ) method, which returns the hour, minutes, and seconds. Java programmers take note: in C#, there’s no need for wrapper classes for basic types such as int (integer). Instead, use reference parameters. Because you can’t return three values, perhaps you can pass in three parameters, let the method modify the parameters, and examine the result in the calling method. Example 4-7 shows a first attempt at this. Example 4-7. Returning values in parameters #region Using directives using System; using System.Collections.Generic; 84 | Chapter 4: Classes and Objects using System.Text; #endregion namespace ReturningValuesInParams { public class Time { // private member variables private int Year; private int Month; private int Date; private int Hour; private int Minute; private int Second; // public accessor methods public void DisplayCurrentTime( ) { System.Console.WriteLine( "{0}/{1}/{2} {3}:{4}:{5}", Month, Date, Year, Hour, Minute, Second ); } public int GetHour( ) { return Hour; } public void GetTime( int h, int m, int s ) { h = Hour; m = Minute; s = Second; } // constructor public Time( System.DateTime dt ) { Year = dt.Year; Month = dt.Month; Date = dt.Day; Hour = dt.Hour; Minute = dt.Minute; Second = dt.Second; } } public class Tester { static void Main( ) { Example 4-7. Returning values in parameters (continued) Passing Parameters | 85 Notice that the Current time in the output is 0:0:0. Clearly, this first attempt did not work. The problem is with the parameters. You pass in three integer parameters to GetTime( ), and you modify the parameters in GetTime( ), but when the values are accessed back in Main( ), they are unchanged. This is because integers are value types, and so are passed by value; a copy is made in GetTime( ). What you need is to pass these values by reference. Two small changes are required. First, change the parameters of the GetTime( ) method to indicate that the parameters are ref (reference) parameters: public void GetTime(ref int h, ref int m, ref int s) { h = Hour; m = Minute; s = Second; } Second, modify the call to GetTime( ) to pass the arguments as references as well: t.GetTime(ref theHour, ref theMinute, ref theSecond); If you leave out the second step of marking the arguments with the keyword ref, the compiler will complain that the argument can’t be converted from an int to a ref int . The results now show the correct time. By declaring these parameters to be ref parameters, you instruct the compiler to pass them by reference. Instead of a copy being made, the parameter in GetTime( ) is a reference to the same variable (theHour) that is created in Main( ). When you change these values in GetTime( ), the change is reflected in Main( ). Keep in mind that ref parameters are references to the actual original value: it is as though you said, “Here, work on this one.” Conversely, value parameters are copies: it is as though you said, “Here, work on one just like this.” System.DateTime currentTime = System.DateTime.Now; Time t = new Time( currentTime ); t.DisplayCurrentTime( ); int theHour = 0; int theMinute = 0; int theSecond = 0; t.GetTime( theHour, theMinute, theSecond ); System.Console.WriteLine( "Current time: {0}:{1}:{2}", theHour, theMinute, theSecond ); } } } Output: 11/17/2007 13:41:18 Current time: 0:0:0 Example 4-7. Returning values in parameters (continued) 86 | Chapter 4: Classes and Objects Overcoming Definite Assignment with out Parameters C# imposes definite assignment, which requires that all variables be assigned a value before they are used. In Example 4-7, if you don’t initialize theHour, theMinute, and theSecond before you pass them as parameters to GetTime( ), the compiler will com- plain. Yet, the initialization that is done merely sets their values to 0 before they are passed to the method: int theHour = 0; int theMinute = 0; int theSecond = 0; t.GetTime( ref theHour, ref theMinute, ref theSecond); It seems silly to initialize these values because you immediately pass them by refer- ence into GetTime where they’ll be changed, but if you don’t, the following compiler errors are reported: Use of unassigned local variable 'theHour' Use of unassigned local variable 'theMinute' Use of unassigned local variable 'theSecond' C# provides the out parameter modifier for this situation. The out modifier removes the requirement that a reference parameter be initialized. The parameters to GetTime( ), for example, provide no information to the method; they are simply a mechanism for getting information out of it. Thus, by marking all three as out parameters, you elimi- nate the need to initialize them outside the method. Within the called method, the out parameters must be assigned a value before the method returns. The following are the altered parameter declarations for GetTime( ): public void GetTime(out int h, out int m, out int s) { h = Hour; m = Minute; s = Second; } And here is the new invocation of the method in Main( ): t.GetTime( out theHour, out theMinute, out theSecond); To summarize, value types are passed into methods by value. ref parameters are used to pass value types into a method by reference. This allows you to retrieve their modified values in the calling method. out parameters are used only to return infor- mation from a method. Example 4-8 rewrites Example 4-7 to use all three. Example 4-8. Using in, out, and ref parameters #region Using directives using System; using System.Collections.Generic; using System.Text; Passing Parameters | 87 #endregion namespace InOutRef { public class Time { // private member variables private int Year; private int Month; private int Date; private int Hour; private int Minute; private int Second; // public accessor methods public void DisplayCurrentTime( ) { System.Console.WriteLine( "{0}/{1}/{2} {3}:{4}:{5}", Month, Date, Year, Hour, Minute, Second ); } public int GetHour( ) { return Hour; } public void SetTime( int hr, out int min, ref int sec ) { // if the passed in time is >= 30 // increment the minute and set second to 0 // otherwise leave both alone if ( sec >= 30 ) { Minute++; Second = 0; } Hour = hr; // set to value passed in // pass the minute and second back out min = Minute; sec = Second; } // constructor public Time( System.DateTime dt ) { Year = dt.Year; Month = dt.Month; Date = dt.Day; Hour = dt.Hour; Minute = dt.Minute; Example 4-8. Using in, out, and ref parameters (continued) 88 | Chapter 4: Classes and Objects SetTime is a bit contrived, but it illustrates the three types of parameters. theHour is passed in as a value parameter; its entire job is to set the member variable Hour, and no value is returned using this parameter. The ref parameter theSecond is used to set a value in the method. If theSecond is greater than or equal to 30, the member variable Second is reset to 0, and the member variable Minute is incremented. You must specify ref on the call and the destination when using refer- ence parameters. Finally, theMinute is passed into the method only to return the value of the member variable Minute, and thus is marked as an out parameter. Second = dt.Second; } } public class Tester { static void Main( ) { System.DateTime currentTime = System.DateTime.Now; Time t = new Time( currentTime ); t.DisplayCurrentTime( ); int theHour = 3; int theMinute; int theSecond = 20; t.SetTime( theHour, out theMinute, ref theSecond ); System.Console.WriteLine( "the Minute is now: {0} and {1} seconds", theMinute, theSecond ); theSecond = 40; t.SetTime( theHour, out theMinute, ref theSecond ); System.Console.WriteLine( "the Minute is now: " + "{0} and {1} seconds", theMinute, theSecond ); } } } Output: 11/17/2007 14:6:24 the Minute is now: 6 and 24 seconds the Minute is now: 7 and 0 seconds Example 4-8. Using in, out, and ref parameters (continued) Overloading Methods and Constructors | 89 It makes perfect sense that theHour and theSecond must be initialized; their values are needed and used. It is not necessary to initialize theMinute,asitisanout parameter that exists only to return a value. What at first appeared to be arbitrary and capri- cious rules now make sense; values are required to be initialized only when their initial value is meaningful. Overloading Methods and Constructors Often, you’ll want to have more than one function with the same name. The most common example of this is to have more than one constructor. In the examples shown so far, the constructor has taken a single parameter: a DateTime object. It would be convenient to be able to set new Time objects to an arbitrary time by pass- ing in year, month, date, hour, minute, and second values. It would be even more convenient if some clients could use one constructor, and other clients could use the other constructor. Function overloading provides for exactly these contingencies. The signature of a method is defined by its name and its parameter list. Two meth- ods differ in their signatures if they have different names or different parameter lists. Parameter lists can differ by having different numbers or types of parameters. For example, in the following code, the first method differs from the second in the num- ber of parameters, and the second differs from the third in the types of parameters: void myMethod(int p1); void myMethod(int p1, int p2); void myMethod(int p1, string s1); A class can have any number of methods, as long as each one’s signature differs from that of all the others. Example 4-9 illustrates the Time class with two constructors: one that takes a DateTime object, and the other that takes six integers. Example 4-9. Overloading the constructor #region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace OverloadedConstructor { public class Time { // private member variables private int Year; private int Month; private int Date; 90 | Chapter 4: Classes and Objects private int Hour; private int Minute; private int Second; // public accessor methods public void DisplayCurrentTime( ) { System.Console.WriteLine( "{0}/{1}/{2} {3}:{4}:{5}", Month, Date, Year, Hour, Minute, Second ); } // constructors public Time( System.DateTime dt ) { Year = dt.Year; Month = dt.Month; Date = dt.Day; Hour = dt.Hour; Minute = dt.Minute; Second = dt.Second; } public Time( int Year, int Month, int Date, int Hour, int Minute, int Second ) { this.Year = Year; this.Month = Month; this.Date = Date; this.Hour = Hour; this.Minute = Minute; this.Second = Second; } } public class Tester { static void Main( ) { System.DateTime currentTime = System.DateTime.Now; Time t1= new Time( currentTime ); t.DisplayCurrentTime( ); Time t2 = new Time( 2007, 11, 18, 11, 03, 30 ); t2.DisplayCurrentTime( ); } } } Example 4-9. Overloading the constructor (continued) Overloading Methods and Constructors | 91 As you can see, the Time class in Example 4-9 has two constructors. If a function’s signature consisted only of the function name, the compiler would not know which constructors to call when constructing t1 and t2. However, because the signature includes the function argument types, the compiler is able to match the constructor call for t1 with the constructor whose signature requires a DateTime object. Likewise, the compiler is able to associate the t2 constructor call with the constructor method whose signature specifies six integer arguments. When you overload a method, you must change the signature (i.e., the name, num- ber, or type of the parameters). You are free, as well, to change the return type, but this is optional. Changing only the return type doesn’t overload the method, and cre- ating two methods with the same signature but differing return types will generate a compile error, as you can see in Example 4-10. Example 4-10. Varying the return type on overloaded methods #region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace VaryingReturnType { public class Tester { private int Triple( int val ) { return 3 * val; } private long Triple( long val ) { return 3 * val; } public void Test( ) { int x = 5; int y = Triple( x ); System.Console.WriteLine( "x: {0} y: {1}", x, y ); long lx = 10; long ly = Triple( lx ); System.Console.WriteLine( "lx: {0} ly: {1}", lx, ly ); } static void Main( ) { [...]... might be tempting to add the keyword new to all your virtual methods This is a bad idea When new appears in the code, it ought to document the versioning of code It points a potential client to the base class to see what you aren’t overriding Using new scattershot undermines this documentation Further, the warning exists to help identify a real issue Abstract Classes Every subclass of Control should implement... The Unified Modeling Language (UML) is a standardized “language” for describing a system or business The part of the UML that is useful for the purposes of this chapter is the set of diagrams used to document the relationships between classes In the UML, classes are represented as boxes The name of the class appears at the top of the box, and (optionally) methods and members can be listed in the sections... Console.WriteLine("The value of s is {0}", s.ToString( )); DisplayValue(s); } } } Output: The value The value The value The value of of of of i is: 5 the object passed in is 5 s is 7 the object passed in is 7 The documentation for Object.ToString( ) reveals its signature: public virtual string ToString( ); 114 | Chapter 5: Inheritance and Polymorphism It is a public virtual method that returns a string and that . part of the UML that is useful for the purposes of this chapter is the set of diagrams used to document the relationships between classes. In the UML, classes are represented as boxes. The name