Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 38 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
38
Dung lượng
826,7 KB
Nội dung
173 ■ ■ ■ CHAPTER 7 Featuresofa.NETClass Y ou’ve been using properties throughout the text, and you looked at an example of an event in Chapter 2. This chapter will go into a bit more detail on properties and events, and will also discuss some featuresof operators unique to C++/CLI, including static operators and how conver- sion operators work in C++/CLI versus classic C++. You’ll also learn about casts and conversions. Properties As you saw in Chapter 2, in terms of object-oriented programming, properties capture the “has-a” relationship for an object. Properties seem a lot like fields to the consumer ofa class. They represent values that can be retrieved and/or written to. You can use them inside the class as well as outside the class (if they are public). There is a special syntax for using them that makes them look like fields, but operations on these “fields” invoke the accessor (get and set) methods that you’ve defined. Properties fully encapsulate the underlying data, whether it’s a single field or something more complex, meaning that you are free to change the underlying field’s representation without affecting the users of the class. Say we want to declare some typical properties we might find in a periodic table of the elements. Listing 7-1 shows how. Listing 7-1. Declaring Properties // declaring_properties.cpp using namespace System; value class ElementType { public: property unsigned int AtomicNumber; property double AtomicWeight; property String^ Name; property String^ Symbol; }; Hogenson_705-2C07.fm Page 173 Wednesday, October 18, 2006 4:50 PM 174 CHAPTER 7 ■ FEATURESOFA.NETCLASS int main() { ElementType oxygen; oxygen.AtomicNumber = 8; oxygen.AtomicWeight = 15.9994; oxygen.Name = "Oxygen"; oxygen.Symbol = "O"; Console::WriteLine("Element: {0} Symbol: {1}", oxygen.Name, oxygen.Symbol); Console::WriteLine("Atomic Number: {0} Atomic Weight: {1}", oxygen.AtomicNumber, oxygen.AtomicWeight); } The output of Listing 7-1 is as follows: Element: Oxygen Symbol: O Atomic Number: 8 Atomic Weight: 15.9994 As you can see, the property is invoked by using its name in a member access expression. You do not call get and set explicitly; they are called for you whenever code specifies a construct that either retrieves the value (for example, using the property in an expression or as a function parameter) or sets the value (when the property is used as an lvalue). Expressions involving properties may not be chained. That is to say, a property cannot be an lvalue and an rvalue at the same time. So, code like this does not work: a = oxygen.AtomicNumber = 8; // error In this example, we use the shorthand syntax for declaring properties that map directly onto a field and have trivial get and set methods. A field is created automatically for such a property, as well as the default get and set methods. Such a field is not intended to be accessed in any way other than through the property. If you use this syntax, you can change it later to the full form of the syntax (for example, to provide an alternative implementation of the property’s underlying data, or add some custom code to the get and set methods) without changing the property’s interface to outside users of the type. In Listing 7-2, we change the AtomicWeight property from a simple double value to a computed value based on the isotopic abundances and number of isotopes. Once the value is computed, the stored result is used. The set method just sets the value as usual, and would perhaps be used if looking up the information from a periodic table. Listing 7-2. Computing a Property Value // periodic_table.cpp using namespace System; using namespace System::Collections::Generic; Hogenson_705-2C07.fm Page 174 Wednesday, October 18, 2006 4:50 PM CHAPTER 7 ■ FEATURESOFA.NETCLASS 175 value class Isotope { public: property double Mass; property unsigned int AtomicNumber; }; value class ElementType { List<Isotope>^ isotopes; List<double>^ isotope_abundance; double atomicWeight; public: property unsigned int AtomicNumber; property String^ Name; property String^ Symbol; property double AtomicWeight { double get() { // Check to see if atomic weight has been calculated yet. if (atomicWeight == 0.0) { if (isotopes->Count == 0) return 0.0; for (int i = 0; i < isotopes->Count; i++) { atomicWeight += isotopes[i].Mass * isotope_abundance[i]; } } return atomicWeight; } void set(double value) { // used if you look up atomic weight instead of calculating it atomicWeight = value; } } // other properties same as before }; You can see how creating a trivial property isn’t like exposing a field directly to users ofa class. If you expose a field directly, you run into problems later if the implementation of the field changes. With a trivial property, you can always later define the get and set methods yourself and change the backing store for the property to suit your needs, while preserving the Hogenson_705-2C07.fm Page 175 Wednesday, October 18, 2006 4:50 PM 176 CHAPTER 7 ■ FEATURESOFA.NETCLASS interface the property presents to other consumers. When defining get and set explicitly, the set method must return void and the get method must return the type of the property. The parameter list for get must be void and the parameter list for set must be the type of the property. Properties need not map onto a field’s value. For example, you could eliminate the atomicWeight field from the class and simply compute the value whenever get is called. The set method would then have to be eliminated. This is fine, though, since if only a get method is defined, the property can be retrieved but not set. As these methods get more complicated, you’ll want to move them out of the class decla- ration. When defining property get and set methods out of the body of the class, use the class name and property name as qualifiers, as in Listing 7-3. Listing 7-3. Defining Property Accessors Outside ofaClass value class ElementType { public: property double AtomicWeight { double get(); } }; double ElementType::AtomicWeight::get() { // same implementation as before } In fact, this notation is how the property accessor is referred to when you need to refer to the method name, such as when you assign a delegate to a get or set method; you use the name of the property in the qualified name, as shown in Listing 7-4. Listing 7-4. Using a Delegate with a Property Accessor // property_accessor_delegate.cpp using namespace System; delegate double ValueAccessor(); value class ElementType { public: property double AtomicWeight; }; Hogenson_705-2C07.fm Page 176 Wednesday, October 18, 2006 4:50 PM CHAPTER 7 ■ FEATURESOFA.NETCLASS 177 int main() { ElementType oxygen; oxygen.AtomicWeight = 15.9994; ValueAccessor^ get_method = gcnew ValueAccessor(oxygen, &ElementType::AtomicWeight::get); Console::WriteLine("{0}", get_method->Invoke()); } Say we’d like to also have some static properties in our Element class. In fact, we’d like to make a periodic table class with a static array property. There is nothing special about a static property; all the rules for static methods and fields apply. Static properties are intended to be used for properties ofa type, not properties ofa particular instance. Listing 7-5 is a first attempt at this. Listing 7-5. Trying to Define a Static Property // property_static.cpp value class ElementType { public: // Periodic Table of the Elements static property array<ElementType>^ PeriodicTable; static ElementType() { PeriodicTable = gcnew array<ElementType>(120); // Initialize each element and its properties. } }; That’s great, but if we later want to change the implementation from an array to a List or Hashtable, we might need to rewrite the code that uses the property. A better way to implement collection-like properties is to use vector properties, also called indexed properties. Using Indexed Properties A special type of property is allowed in C++/CLI that enables properties to act like arrays. You can also use indexed properties to provide array indexing on objects, the equivalent of defining the array indirection operator (operator[]) for your type. To make a property support the indexing syntax, use the square brackets in the property declaration. Inside the square brackets, put the type you will use as the index. You can index on any type. Listing 7-6 shows a simple indexed property named ordinal. Note the type of the index appears inside square brackets, and the index is used as the first parameter of both the get and set methods. Hogenson_705-2C07.fm Page 177 Wednesday, October 18, 2006 4:50 PM 178 CHAPTER 7 ■ FEATURESOFA.NETCLASS Listing 7-6. Using an Indexed Property // properties_indexed1.cpp using namespace System; ref class Numbers { array<String^>^ ordinals; public: Numbers() { ordinals = gcnew array<String^> { "zero", "one", "two", "three" }; } property String^ ordinal[unsigned int] { String^ get(unsigned int index) { return ordinals[index]; } void set(unsigned int index, String^ value) { ordinals[index] = value; } } }; int main() { Numbers^ nums = gcnew Numbers(); // Access the property values using the indexer // with an unsigned int as the index. Console::WriteLine( nums->ordinal[0] ); } Here is the output of Listing 7-6: zero You can also define a default indexed property by naming the property default, which enables the index to be used directly on the instance of the object (see Listing 7-7). Whether you are accessing a default indexed property using a handle or a variable declared with stack semantics, you can use the array indirection operator directly. Hogenson_705-2C07.fm Page 178 Wednesday, October 18, 2006 4:50 PM CHAPTER 7 ■ FEATURESOFA.NETCLASS 179 Listing 7-7. Using a Default Property // properties_indexed2.cpp using namespace System; ref class Numbers { array<String^>^ ordinals; public: Numbers() { ordinals = gcnew array<String^> { "zero", "one", "two", "three" }; } property String^ default[unsigned int] { String^ get(unsigned int index) { return ordinals[index]; } void set(unsigned int index, String^ value) { ordinals[index] = value; } } }; int main() { Numbers nums; // Access property using array indexing operators on the // instance directly. Console::WriteLine( nums[0] ); // If using a handle, you can still use array syntax. Numbers^ nums2 = gcnew Numbers(); Console::WriteLine( nums2[1] ); // You can also use the name "default" and access like a // named property. Console::WriteLine( nums.default[2] ); Console::WriteLine( nums2->default[3] ); } Hogenson_705-2C07.fm Page 179 Wednesday, October 18, 2006 4:50 PM 180 CHAPTER 7 ■ FEATURESOFA.NETCLASS The output of Listing 7-7 is as follows: zero one two three Listing 7-8 shows aclass with an indexed property whose backing store is a collection class. The indexed property on the class PeriodicTable invokes the default indexed property on a.NET Framework collection class, Hashtable (here accessed through the interface IDictionary). The ElementType class now overrides the ToString method on Object to allow custom output. Chapter 8 discusses the override keyword. Listing 7-8. Backing a Property with a Collection // periodic_table.cpp using namespace System; using namespace System::Collections; value class ElementType { public: property unsigned int AtomicNumber; property double AtomicWeight; property String^ Name; property String^ Symbol; // You cannot use initializer list syntax to initialize properties. ElementType(String^ name, String^ symbol, double a, double n) { AtomicNumber = n; AtomicWeight = a; Name = name; Symbol = symbol; } // Override the ToString method (you'll learn more about the override // keyword in the next chapter). virtual String^ ToString() override { return String::Format( "Element {0} Symbol {1} Atomic Number {2} Atomic Weight {3}", Name, Symbol, AtomicNumber, AtomicWeight); } }; Hogenson_705-2C07.fm Page 180 Wednesday, October 18, 2006 4:50 PM CHAPTER 7 ■ FEATURESOFA.NETCLASS 181 ref class PeriodicTable { private: Hashtable^ table; public: PeriodicTable() { table = gcnew Hashtable(); ElementType element("Hydrogen", "H", 1.0079, 1); // Add to the Hashtable using the key and value. table->Add(element.Name, element); // Add the other elements . } property ElementType default[String^] { ElementType get(String^ key) { return safe_cast<ElementType>( table[key] ); } } }; int main() { PeriodicTable^ table = gcnew PeriodicTable(); // Get the element using the indexed property and print it. Console::WriteLine( table["Hydrogen"] ); } The output of Listing 7-8 is shown here: Element Hydrogen Symbol H Atomic Number 1 Atomic Weight 1.0079 Now suppose we want to implement a table of the isotopes, as envisioned in Chapter 2. Isotopes are different versions of the same element, so there is a many-to-one relationship between isotopes and elements. Isotopes are distinguished by a number, the isotope number, which is equal to the number of protons plus the number of neutrons. The number of protons determines the type of element, and the different isotopes of an element just vary by the number Hogenson_705-2C07.fm Page 181 Wednesday, October 18, 2006 4:50 PM 182 CHAPTER 7 ■ FEATURESOFA.NETCLASSof neutrons. In Listing 7-9, a hashtable is used to store the various isotopes. The key is based on the element type and the isotope number, which uniquely identifies the isotope. For example, for carbon-14, the key is “C14”. Since you can have more than one index variable, separated by commas, in an indexed property, we could look up an isotope by the name of the element and the isotope number, as the ElementIsotope property in Listing 7-9 shows. The key is computed by appending the element symbol and the isotope number, which are the arguments of the indexed property. Listing 7-9. Using Multiple Indexes // isotope_table.cpp using namespace System; using namespace System::Collections::Generic; value class Isotope { public: property unsigned int IsotopeNumber; property unsigned int AtomicNumber; }; ref class IsotopeTable { private: Dictionary<String^, Isotope>^ isotopeTable; public: IsotopeTable() { isotopeTable = gcnew Dictionary<String^, Isotope>(); // Add the elements and their isotopes . // Additional code for the elements is assumed. for each (ElementType element in PeriodicTable::Elements) { // Add each isotope to the isotopes table. for each (Isotope isotope in element.Isotopes) { isotopeTable->Add(element.Name + isotope.IsotopeNumber, isotope); } } } Hogenson_705-2C07.fm Page 182 Wednesday, October 18, 2006 4:50 PM [...]... pattern allows you to set up a callback function that is called when the function called by the delegate completes The BeginInvoke has a signature that is determined by the delegate declaration BeginInvoke has the same parameters as the usual Invoke function, plus two additional parameters: the first is an AsyncCallback class and the second is the delegate EndInvoke has only one parameter of type IAsyncResult... Page 203 Wednesday, October 18, 2006 4:50 PM CHAPTER 7 ■ FEATURES OFA NET CLASS Reserved Names Whenever properties or events are declared in a class, certain methods get created that implement the properties and events Thus, certain names become reserved in aclass that has these properties or events In aclass with a property named P, the names get_P and set_P are reserved, and in aclass with an... operating system Within C++/CLI there are a number of abstractions that help implement event-driven programming C++/CLI events are defined as members of a managed type Events in C++/CLI must be defined as members ofa managed type The idea of defining an event in aclass is to associate a method that is to be called (or multiple methods that are to be called) when those events are raised On a practical... delegate is not the function itself; it simply represents the address ofa function to call, along with a specific object whose method is to be called, if applicable Delegates are strongly typed, in that the parameter types and return type are part of the type ofa delegate A delegate variable may only be assigned to a function that matches the delegate signature Delegates may not be used to designate a. .. C++/CLI allows you to define static operators on aclass This avoids the need for some operators to be global, such as addition between aclass type and a primitive type Usually, these functions require access to the internals of a class, so the concept of friend functions and friend classes is often used in C++ to allow external operator functions to access the internals ofa type To illustrate the... for example if you have a delegate like this one: delegate void MyDelegate(R^ r); the invoke methods have the following signatures: AsyncResult^ BeginInvoke(R^, AsyncCallback^, MyDelegate^ ); void EndInvoke(IAsyncResult^); Hogenson_705-2C07.fm Page 189 Wednesday, October 18, 2006 4:50 PM CHAPTER 7 ■ FEATURES OFA NET CLASS The classes AsyncCallback and AsyncResult and the associated interface IAsyncResult... be called on aclass when the class is on the right side of the expression In C++/CLI, the operators that in classic C++ you would define as global friend functions, you define as static operators in the class This is considered a superior design in that you do not need to make any special exceptions to the encapsulation of the private data for aclass in order to support commutative operators that... Listing 7-22, the addition operators between Complex and double are declared as static operators in the class These operators would have been global friend functions in a native C++ class In addition, the operator for adding two complex numbers could also be defined as static, as in Listing 7-22, rather than as a member operator as it would be in classic C++ Listing 7-22 Defining a Static Operator // complex.h... representing a decay of the atom, releasing two protons and two neutrons in the form of an alpha particle This changes the atomic number and isotope number, which are updated As you recall, the decay events in a radioactive atom were modeled using delegates and events The delegate or event was used to call the designated decay method The next section covers delegates and events in more detail Delegates and Events... classes (such as RadioactiveAtom) // can change the atomic number protected: void set(unsigned int n) { atomic_number = n; } } }; ref class RadioactiveAtom : Atom { // other stuff 183 Hogenson_705-2C07.fm Page 184 Wednesday, October 18, 2006 4:50 PM 184 CHAPTER 7 ■ FEATURES OFA NET CLASS public: void AlphaDecay() { AtomicNumber -= 2; IsotopeNumber -= 4; } }; AlphaDecay is a function representing a . Wednesday, October 18, 2006 4:50 PM CHAPTER 7 ■ FEATURES OF A .NET CLASS 189 The classes AsyncCallback and AsyncResult and the associated interface IAsyncResult. declare and use a simple delegate. Delegates are actually instances of the .NET Framework class System::MulticastDelegate. The name “multicast” implies that