1. Trang chủ
  2. » Công Nghệ Thông Tin

Accelerated VB 2005 phần 3 docx

43 246 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 43
Dung lượng 364,5 KB

Nội dung

System.Console.WriteLine("Circle.Draw") End Sub End Class Public Class Drawing Private Shapes As ArrayList Public Sub New() Shapes = New ArrayList End Sub Public ReadOnly Property Count() As Integer Get Return Shapes.Count End Get End Property Default Public ReadOnly Property Item(ByVal Index As Integer) As GeometricShape Get Return CType(Shapes(Index), GeometricShape) End Get End Property Public Sub Add(ByVal Shape As GeometricShape) Shapes.Add(Shape) End Sub End Class Public Class EntryPoint Shared Sub Main() Dim Rectangle As Rectangle = New Rectangle() Dim Circle As Circle = New Circle() Dim Drawing As Drawing = New Drawing() Dim i As Integer = 0 Drawing.Add(Rectangle) Drawing.Add(Circle) For i = 0 To Drawing.Count - 1 Step 1 Dim Shape As GeometricShape = Drawing(i) Shape.Draw() Next End Sub End Class CHAPTER 4 ■ CLASSES AND STRUCTURES 63 801-6CH04.qxd 2/15/07 9:57 PM Page 63 As shown, you can access the elements of the Drawing object in the Main() routine as if they were inside a normal array. Also, since this indexer only has a Get accessor, it is read-only. Keep in mind that if the collection holds onto references to objects, the client code can still change the state of the contained object through that reference. But since the indexer is read- only, the client code cannot swap out the object reference at a specific index with a reference to a completely different object. One difference is worth noting between a real array and the indexer. You cannot pass the results of calling an indexer on an object as a ByRef parameter to a method as you can do with a real array. This is similar to the same restriction placed on properties. Partial Classes Partial classes are a new addition to VB. So far, you’ve seen how to define classes in one single file. Until now, it was impossible to split the definition of a class across multiple files. At first, such a convenience may not seem worthwhile. After all, if a class has become so large that the file is hard to manage, that may be an indication of poor design. But arguably, the main reason partial classes were introduced is to support code-generation tools. Normally, when you work within the confines of the IDE, the IDE tries to help you out by generating some code for you. For example, a wizard generates helpful DataSet derived classes when using ADO.NET facilities. The classic problem has always been editing the resulting code generated by the tool. It was always a dangerous proposition to edit the output from the tool, because any time the parameters to the tool change, the tool regenerates the code, thus overwriting any changes made. This is definitely not desired. Previously, the only way to work around this was to use some form of reuse, such as inheritance or containment, thus inherit- ing a class from the class produced by the code-generation tool. Many times these were not natural solutions to the problem, and the code generated was not designed to take inheritance into consideration. Now, you can slip the Partial keyword into the class definition right before the Class key- word, and voilà, you can split the class definition across multiple files. One requirement is that each file that contains part of the partial class must use the Partial keyword, and you must define all of the partial pieces within the same namespace, if you declare them in a name- space at all. Now, with the addition of the Partial keyword, the code generated from the code-generation tool can live in a separate file from the additions to that generated class, and when the tool regenerates the code, you don’t lose your changes. You should know some things about the process the compiler goes through to assemble partial classes into a whole class. You must compile all the partial pieces of a class together at once so the compiler can find all of the pieces. For the most part, all of the members and aspects of the class are merged together using a union operation. Therefore, they must coexist together as if you had declared and defined them all in the same file. Base interface lists are unioned together. However, since a class can have one base class at most, if the partial pieces list a base class, they must all list the same base class. Other than those obvious restrictions, you’ll probably agree that partial classes are a welcome addition. Value Type Definitions A value type is a lightweight type that is allocated on the stack instead of on the heap. The only exception to this rule is a value type that is a field in an object that lives on the heap. Value CHAPTER 4 ■ CLASSES AND STRUCTURES64 801-6CH04.qxd 2/15/07 9:57 PM Page 64 types include the VB numeric data types such as Integer, Enum, and Structure. A value type is a type that behaves with value semantics. That is, when you assign a value type variable to another value type variable, the contents of the source are copied into the destination and a full copy of the instance is made. This is in contrast to reference types, or object instances, where the result of copying a reference type variable to another is that there is now a new ref- erence to the same object. Also, when you pass a value type as a parameter to a method, the method body receives a local copy of the value, unless the parameter was declared as a ByRef parameter. In VB, you declare a structure using the Structure keyword rather than the Class keyword. On the whole, the syntax of defining a structure is the same as a class, with some notable exceptions as you’ll soon see. A structure cannot declare a base class. Also, a structure is implicitly sealed. That means that nothing else can derive from a structure. Internally, a struc- ture derives from System.ValueType, which in turn extends System.Object. This is so that ValueType can provide implementations of Equals() and GetHashCode(), among others, which are meaningful for value types. The section titled “System.Object” covers the nuances involved with implementing the methods inherited from System.Object for a value type. Like classes, you can declare structures in partial pieces, and the same rules for partial pieces apply to structures as they do to classes. Constructors Types defined as structures can have static constructors just like classes. Structures can also have instance constructors, with one notable exception. They cannot have a user-defined default, parameterless constructor, nor can they have instance field initializers in the structure definition. Static field initializers are permitted, though. Parameterless constructors are not necessary for value types, since the system provides one, which simply sets the fields of the value to their default values. In all cases, that amounts to setting the bits of the field’s storage to 0. So, if a structure contains an Integer, the default value will be 0. If a structure contains a reference type field, the default value will be Nothing. Each structure gets this implicit, para- meterless constructor that takes care of this initialization. It’s all part of the language’s endeavors to create verifiably type-safe code. The following code shows the use of the default constructor: Imports System Public Structure Square Private mWidth As Integer Private mHeight As Integer Public Property Width() As Integer Get Return mWidth End Get Set(ByVal Value As Integer) mWidth = Value End Set End Property CHAPTER 4 ■ CLASSES AND STRUCTURES 65 801-6CH04.qxd 2/15/07 9:57 PM Page 65 Public Property Height() As Integer Get Return mHeight End Get Set(ByVal Value As Integer) mHeight = Value End Set End Property End Structure Public Class EntryPoint Shared Sub Main() Dim sq As New Square() Console.WriteLine("{0} x {1}", sq.Width, sq.Height) sq.Width = 1 sq.Height = 2 Console.WriteLine("{0} x {1}", sq.Width, sq.Height) End Sub End Class Running this example yields the following results: 0 x 0 1 x 2 The first result demonstrates that the system-provided constructor has initialized the fields to the default value of 0, while the second result displays the assignment of these fields in the code. The Meaning of Me The Me keyword within class methods behaves as a constant, read-only value that contains a reference to the current object instance. Even though instance constructors in structure value types cannot use the MyBase keyword to call base class constructors, they can have an initial- izer. It is valid for the initializer to use the Me keyword to call other constructors on the same structure during initialization, as in the following example: Public Structure ComplexNumber Private Real As Double Private Imaginary As Double Public Sub New(ByVal Real As Double, ByVal Imaginary As Double) Me.Real = Real Me.Imaginary = Imaginary End Sub CHAPTER 4 ■ CLASSES AND STRUCTURES66 801-6CH04.qxd 2/15/07 9:57 PM Page 66 Public Sub New(ByVal Real As Double) Me.New(Real, 0) End Sub End Structure Public Class EntryPoint Shared Sub Main() Dim valA As ComplexNumber = New ComplexNumber(1) End Sub End Class The previous code introduces an initializer that calls the first constructor from the second one, which only assigns the Real value. When an instance constructor contains an initializer, the Me keyword behaves as a ByRef parameter in that constructor’s body. And, since it is a ByRef parameter, the compiler can assume that the value has been initialized properly before entry into the method’s code block. In essence, the initialization burden is deferred to the first con- structor, whose duty it is to make sure it initializes all fields of the value. One last note to consider is that even though the system generates a default, parameter- less initializer, you can’t call it using the Me keyword. Finalizers Value types are not allowed to have a finalizer and are removed from the stack as soon as they go out of scope. The concept of finalization, or nondeterministic destruction, is reserved for instances of classes, or objects, because that is how management of the heap works. If struc- tures had finalizers, the runtime would have to manage the calling of the finalizer each time the value goes out of scope. Keep in mind that you want to be careful about initializing resources within structure constructors. Consider a value type that has a field, which is a handle to some sort of low-level system resource. Suppose this low-level resource is allocated, or acquired, in a special con- structor that accepts parameters. You now have a couple of problems to deal with. Since you cannot create a default, parameterless constructor, how can you possibly acquire the resource when the user creates an instance of the value without using one of the custom constructors? The answer is, you cannot. The second problem is that you have no automatic trigger to clean up and release the resource, since you have no destructor. Interfaces Although it’s illegal for a structure to derive from another class, it can still implement inter- faces. Supported interfaces are listed in the same way as they are for classes, in a base interface list after the structure identifier. Generally, supporting interfaces for structures is the same as supporting interfaces for classes. Chapter 7 covers interfaces in detail. Implementing interfaces on structures has performance implications; specifically, it incurs a boxing opera- tion to call methods through an interface reference on the structure value instances. CHAPTER 4 ■ CLASSES AND STRUCTURES 67 801-6CH04.qxd 2/15/07 9:57 PM Page 67 Boxing and Unboxing All types within the CLR fall into one of two categories: reference types (objects) or value types (values). You define objects using classes, and you define values using structures. A clear divide exists between these two. Objects live on the memory heap and are managed by the garbage collector. Values normally live in temporary storage spaces, such as on the stack. The one notable exception already mentioned is that a value type can live on the heap as long as it is contained as a field within an object. However, it is not autonomous, and the GC doesn’t control its lifetime directly. Consider the following code: Public Class EntryPoint Shared Sub Print(ByVal obj As Object) System.Console.WriteLine("{0}", obj.ToString()) End Sub Shared Sub Main() Dim x As Integer = 42 Print(x) End Sub End Class It looks simple enough. In Main(), there is an Integer, which is an alias for System.Int32, and it is a value type. You could have just as well declared x as type System.Int32. The space allocated for x is on the local stack. You then pass it as a parameter to the Print() method. The Print() method takes an object reference and simply sends the results of calling ToString() on that object to the console. Let’s analyze this. Print() accepts an object reference, which is a reference to a heap-based object. Yet, you’re passing a value type to the method. How is this possible? The key is a concept called boxing. At the point where a value type is defined, the CLR creates a runtime-created wrapper class to contain the value type. Instances of the wrapper live on the heap and are commonly called boxing objects. This is the CLR’s way of bridging the gap between value types and reference types. The boxing object behaves just like any other reference type in the CLR. Also, note that the boxing type implements the interfaces of the contained value type. The boxing type is a class type that is generated internally by the virtual execution system of the CLR at the point where the contained value type is defined. The CLR then uses this internal class type when it performs boxing operations as needed. The most important thing to keep in mind with boxing is that the boxed value is a copy of the original. Therefore, any changes made to the value inside the box are not propagated back to the original value. For example, consider this slight modification to the previous code: Public Class EntryPoint Shared Sub PrintAndModify(ByVal obj As Object) System.Console.WriteLine("{0}", obj.ToString()) Dim x As Integer = CType(obj, Integer) x = 21 End Sub CHAPTER 4 ■ CLASSES AND STRUCTURES68 801-6CH04.qxd 2/15/07 9:57 PM Page 68 Shared Sub Main() Dim x As Integer = 42 PrintAndModify(x) PrintAndModify(x) End Sub End Class This output from the preceding code might surprise you: 42 42 You might expect the second value sent to the console to be 21, but the fact is, the original value, x, declared and initialized in Main(), is never changed. As you pass it to the PrintAnd- Modify method, it is boxed, since the PrintAndModify method takes an object as its parameter. Even though PrintAndModify() takes a reference to an object that you can modify, the object it receives is a boxing object that contains a copy of the original value. The preceding code also introduces another operation called unboxing in the PrintAndModify method. Since the value is boxed inside of an instance of an object on the heap, you can’t change the value because the only methods supported by that object are methods that System.Object implements. Techni- cally, it also supports the same interfaces that System.Int32 supports. Therefore, you need a way to get the value out of the box. You can accomplish this syntactically with casting by using the CType function. Notice that you cast the object instance back into an Integer, and the compiler is smart enough to know that what you’re really doing is unboxing the value type. The operation of unboxing a value is the exact opposite of boxing. The value in the box is copied into an instance of the value on the local stack. Again, any changes made to this unboxed copy are not propagated back to the value contained in the box. Now, you can see how boxing and unboxing can really become confusing. As shown, the code’s behavior is not obvious to the casual observer who is unfamiliar with the fact that boxing and unboxing are going on. What’s worse is that two copies of the Integer are created between the time the call to PrintAndModify() is initiated and the time that the Integer is manipulated in the method. The first copy is the one put into the box. The second copy is the one created when the boxed value is copied out of the box. Technically, it’s possible to modify the value that is contained within the box. However, you must do this through an interface. The runtime-generated box that contains the value also implements the interfaces that the value type implements and forwards the calls to the con- tained value. So, you could do the following: Public Interface IModifyMyValue Property X() As Integer End Interface Public Structure MyValue Implements IModifyMyValue Public _x As Integer Public Property X() As Integer Implements IModifyMyValue.X CHAPTER 4 ■ CLASSES AND STRUCTURES 69 801-6CH04.qxd 2/15/07 9:57 PM Page 69 Get Return _x End Get Set(ByVal Value As Integer) _x = Value End Set End Property Public Overloads Overrides Function ToString() As String Dim output As System.Text.StringBuilder = New System.Text.StringBuilder output.AppendFormat("{0}", _x) Return output.ToString End Function End Structure Public Class EntryPoint Shared Sub Main() Dim MyVal As MyValue = New MyValue MyVal.X = 123 Dim obj As Object = MyVal System.Console.WriteLine("{0}", obj.ToString) Dim IFace As IModifyMyValue = CType(obj, IModifyMyValue) IFace.X = 456 System.Console.WriteLine("{0}", obj.ToString) Dim NewVal As MyValue = CType(obj, MyValue) System.Console.WriteLine("{0}", NewVal.ToString) End Sub End Class You can see that the output from the code is as follows: 123 456 456 As expected, you’re able to modify the value inside the box using the interface named IModifyMyValue. However, it’s not the most straightforward process. And keep in mind that before you can obtain an interface reference to a value type, you must box it. This makes sense if you think about the fact that references to interfaces are object reference types. CHAPTER 4 ■ CLASSES AND STRUCTURES70 801-6CH04.qxd 2/15/07 9:57 PM Page 70 When Boxing Occurs Since boxing is handled implicitly for you, it’s important to know the instances when VB boxes a value. Basically, a value gets boxed when one of the following conversions occur: • Conversion from a value type to an object reference • Conversion from a value type to a System.ValueType reference • Conversion from a value type to a reference to an interface implemented by the value type • Conversion from an enum type to a System.Enum reference In each case, the conversion normally takes the form of an assignment expression. The first two cases are fairly obvious, since the CLR is bridging the gap by turning a value type instance into a reference type. The third one can be a little surprising. Any time you implicitly cast your value into an interface that it supports, you incur the penalty of boxing. Consider the following code: Public Interface IPrint Sub Print() End Interface Public Structure MyValue Implements IPrint Public x As Integer Public Sub Print() Implements IPrint.Print System.Console.WriteLine("{0}", x) End Sub End Structure Public Class EntryPoint Shared Sub Main() Dim MyVal As MyValue = New MyValue MyVal.x = 123 'No Boxing MyVal.Print() 'Boxing occurs Dim Printer As IPrint = MyVal Printer.Print() End Sub End Class The first call to Print() is done through the value reference, which doesn’t incur boxing. However, the second call to Print() is done through an interface. The boxing takes place at CHAPTER 4 ■ CLASSES AND STRUCTURES 71 801-6CH04.qxd 2/15/07 9:57 PM Page 71 the point where you obtain the interface. At first, it looks like you can easily sidestep the box- ing operation by not acquiring an explicit reference typed on the interface type. This is true in this case, since Print() is also part of the public contract of MyValue. However, had you imple- mented the Print() method as an explicit interface, which Chapter 7 covers, then the only way to call the method would be through the interface reference type. So, it’s important to note that any time you implement an interface on a value type explicitly, you force the clients of your value type to box it before calling through that interface. The following example demonstrates this: Public Interface IPrint Sub Print() End Interface Public Structure MyValue Implements IPrint Public x As Integer Sub Print() Implements IPrint.Print System.Console.WriteLine("{0}", x) End Sub End Structure Public Class EntryPoint Shared Sub Main() Dim MyVal As MyValue = New MyValue MyVal.x = 123 'Must box the value Dim Printer As IPrint = MyVal Printer.Print() End Sub End Class As another example, consider that the System.Int32 type supports the IConvertible interface. However, most of the IConvertible interface methods are implemented explicitly. Therefore, even if you want to call an IConvertible method, such as IConvertible.ToBoolean() on a simple Integer, you must box it first. ■Note Typically, you want to rely upon the external class System.Convert to do a conversion like the one mentioned previously. Calling directly through IConvertible is only mentioned as an example. CHAPTER 4 ■ CLASSES AND STRUCTURES72 801-6CH04.qxd 2/15/07 9:57 PM Page 72 [...]... VarArgs(42, 43, 44) VarArgs(44, 56, 23, 234 , 45, 1 23) End Sub Shared Sub VarArgs(ByVal val1 As Integer, ByVal ParamArray vals As Integer()) Console.WriteLine("val1: {0}", val1) For Each i As Integer In vals Console.WriteLine("vals[]: {0}", i) Next Console.WriteLine() End Sub End Class Here are the results from the previous example: val1: 42 val1: 42 vals[]: 43 vals[]: 44 val1: 44 vals[]: 56 vals[]: 23 vals[]:... It contains all of its values as references to type Object If you were to insert a bunch of value types into it, they would all be boxed Thankfully, generics, which are new to VB 2005 and NET 2.0 and are covered in Chapter 13, can solve this inefficiency for you However, note that boxing is inefficient, and you should avoid it as much as possible Since boxing is an implicit operation, it takes a keen... {0}", i) Next Console.WriteLine() End Sub End Class Here are the results from the previous example: val1: 42 val1: 42 vals[]: 43 vals[]: 44 val1: 44 vals[]: 56 vals[]: 23 vals[]: 234 vals[]: 45 vals[]: 1 23 89 801-6CH05.qxd 90 3/ 2/07 8:19 AM Page 90 CHAPTER 5 ■ METHODS, PROPERTIES, AND FIELDS In each case, VarArgs() is called successfully, but also in each case, the array references by the vals parameter... allow for read access, while Set accessors allow for write access to your properties Accessors can provide read-write, read-only, or write-only access to your properties Beginning with Visual Basic 2005 (VB 2005) , Get and Set accessors can have different accessibility levels, provided that the Set accessor is more restrictive Get blocks are called when the client of the object reads the property As you... can support either deterministic destruction or nondeterministic destruction Chapter 5 will begin by exploring how methods are implemented in VB After methods, we’ll cover properties and fields, which allow you to control the state of your objects 801-6CH05.qxd 3/ 2/07 8:19 AM CHAPTER Page 85 5 Methods, Properties, and Fields N ext up are methods, properties, and fields Methods define procedures that... in and of themselves The inefficiency stems from the fact that VB typically combines that unboxing operation with a copy of the value System.Object Every object in the CLR derives from System.Object Object is the base type of every type The Object keyword is an alias for System.Object It can be convenient that every type in the CLR and in VB derives from Object For example, you can treat a collection... Studio debugger can call it to display information at debug time In fact, ToString() is most useful for debugging purposes and rarely useful outside of that The Finalize method deserves special mention VB 2005 doesn’t allow you to explicitly override this method on structure types If you override this method for a class, the garbage collector will execute your finalizer before destroying your object 1... conversion is bad during overload resolution; just use it judiciously and sparingly Overridable and MustOverride Methods VB implements the notion of Overridable (virtual) and MustOverride (abstract) methods, just as other object-oriented (OO) languages do That’s no surprise at all, since VB is an objectoriented language, and overridable methods are the primary mechanism for implementing dynamic polymorphism... contrast to MustOverride methods, are required to have an implementation associated with them Overridable methods, along with interfaces, are the only means of implementing polymorphism within VB 801-6CH05.qxd 3/ 2/07 8:19 AM Page 91 CHAPTER 5 ■ METHODS, PROPERTIES, AND FIELDS Overrides and Shadows To override a method in a derived class, you must tag the method with the Overrides modifier If you don’t,... instead That is why A::SomeMethod() is the method that gets called Had B::SomeMethod() been marked as Overrides, then the code 91 801-6CH05.qxd 92 3/ 2/07 8:19 AM Page 92 CHAPTER 5 ■ METHODS, PROPERTIES, AND FIELDS would have called B::SomeMethod() instead Since VB defaults to using the Shadows modifier when none of them are present, it throws off the warning to get your attention Finally, the Shadows modifier . into it, they would all be boxed. Thankfully, generics, which are new to VB 2005 and .NET 2.0 and are covered in Chap- ter 13, can solve this inefficiency for you. However, note that boxing is inefficient,. there is an Integer, which is an alias for System.Int32, and it is a value type. You could have just as well declared x as type System.Int32. The space allocated for x is on the local stack = New MyValue MyVal.x = 1 23 'Must box the value Dim Printer As IPrint = MyVal Printer.Print() End Sub End Class As another example, consider that the System.Int32 type supports the IConvertible interface.

Ngày đăng: 09/08/2014, 12:22

w