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

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

.pdf
Скачиваний:
70
Добавлен:
16.08.2013
Размер:
24.18 Mб
Скачать

258

C H A P T E R 7 C O L L E C T I O N S

Note Microsoft in the documentation states that the ListDictionary is faster than a Hashtable if you restrict the size to ten or less. My take on this is that when you plan on having more than ten elements, it is probably better to use a Hashtable.

In fact, the .NET Framework class library provides a specialized collection called the HybridDictionary that starts off as a ListDictionary when the number of entries is small and automatically changes to a Hashtable when the number of elements increases.

The ListDictionary has few methods, all of which you learned about earlier in this chapter.

A feature that the ListDictionary shares with the Hashtable (and the SortedList), which you haven’t covered already, is the capability to add key/value pairs using the default index property. As you might expect, when the key passes, the value is changed because the default index property already exists. (What you might not expect is that if the key is unique, then the key/value pair is added.)

Caution Add() works when adding a unique key only. Duplicate keys passed to the Add() method throw an ArgumentException instead of replacing the value.

Listing 7-5 shows the ListDictionary in action and demonstrates the functionality described previously.

Listing 7-5. Working with ListDictionary

#using <system.dll>

using namespace System;

using namespace System::Collections;

using namespace System::Collections::Specialized;

void main()

{

ListDictionary ^ldict = gcnew ListDictionary();

ldict->Add("A", "First"); ldict->Add("B", "Second"); ldict->Add("C", "Third"); ldict["D"] = "Fourth";

try {

ldict->Add("C", "Third Replaced");

}

catch (ArgumentException ^e)

{

Console::WriteLine("ldict->Add(\"C\", \"Third Replaced\");"); Console::WriteLine("Throws exception: {0}", e->Message);

}

ldict["B"] = "Second Replaced";

C H A P T E R 7 C O L L E C T I O N S

259

Console::WriteLine("\nEnumerate");

IEnumerator ^keys = ldict->Keys->GetEnumerator(); IEnumerator ^vals = ldict->Values->GetEnumerator(); while ( keys->MoveNext() && vals->MoveNext())

{

Console::WriteLine("{0}\t\t{1}", keys->Current, vals->Current);

}

Console::WriteLine();

}

Figure 7-6 shows the results of the ListDict.exe program.

Figure 7-6. Results of ListDict.exe

StringCollection

When you plan to maintain many strings, it might be more advantageous to use a StringCollection than any of the other collection types (unless you want key/value access), as a StringCollection is designed to specifically handle strings. A StringCollection resembles a simplified ArrayList in many ways, except that it lacks a few of its methods and uses the StringEnumerator instead of the

IEnumerator.

Listing 7-6 shows the StringCollection in action. As you can see, it has many of the same methods as an ArrayList and is strongly typed to strings.

Listing 7-6. Working with StringCollection

#using <system.dll>

using namespace System;

using namespace System::Collections;

using namespace System::Collections::Specialized;

void main()

{

StringCollection ^strcol = gcnew StringCollection();

strcol->Add("The first String");

260

C H A P T E R 7 C O L L E C T I O N S

array<String^>^ tmpstr = gcnew array<String^> {"Third", "Fourth" }; strcol->AddRange(tmpstr);

strcol->Insert(1, "Second");

strcol[0] = "First";

StringEnumerator ^strenum = strcol->GetEnumerator(); while ( strenum->MoveNext())

{

Console::WriteLine(strenum->Current);

}

Console::WriteLine("\n'for each' works as well");

for each (String^ s in strcol) Console::WriteLine(s);

Console::WriteLine();

}

Figure 7-7 shows the results of the StringColl.exe program.

Figure 7-7. Results of StringColl.exe

StringDictionary

The StringDictionary sounds impressive, don’t you think? It’s really just a Hashtable strongly typed and designed specifically for strings. There’s nothing new here, other than pretty well all methods expect the String type instead of the Object type.

Listing 7-7 shows the StringDictionary in action. This example shows one of the many ways of displaying the StringDictionary in alphabetical order, as a StringDictionary does not sort its entries. If you recall, a Hashtable works by simply looking up the key to find its value, and no sorting occurs. In the example, you get a copy of all the keys and place them into an ArrayList. Then, you use the ArrayList’s built-in Sort() method.

C H A P T E R 7 C O L L E C T I O N S

261

Listing 7-7. Working with StringDictionary

#using <system.dll>

using namespace System;

using namespace System::Collections;

using namespace System::Collections::Specialized;

void main()

{

StringDictionary ^strdict = gcnew StringDictionary();

strdict->Add("Dog", "Four leg, hydrant loving, barking, mammal"); strdict->Add("Frog", "Green, jumping, croaking, amphibian");

strdict["Crocodile"] = "Ugly, boot origin, snapping, reptile";

ArrayList ^alist = gcnew ArrayList(); alist->AddRange(strdict->Keys); alist->Sort();

for (int i = 0; i < alist->Count; i++)

{

Console::WriteLine("{0,10}:\t{1}", alist[i], strdict[(String^)alist[i]]);

}

Console::WriteLine();

}

Figure 7-8 shows the results of the StringDict.exe program.

Figure 7-8. Results of StringDict.exe

NameValueCollection

Let’s finish off the standard collections with one final type: NameValueCollection. This collection is similar in many ways to the StringDictionary. It uses a Hashtable internally and is optimized for handling string. Where it differs is in its ability to have multiple values for a single key.

You can add a key/value pair to a NameValueCollection using the Add() or Set() method, or the default index property. However, only the Add() method allows multiple values to be assigned to a single key:

262

C H A P T E R 7 C O L L E C T I O N S

nvCol->Set("Flower", "Rose"); nvCol->Add("Animal", "Dog"); nvCol["Fruit"] = "Plum";

You can update the value of a key using either the default index property or the Set() method, but in both cases only a single value can be assigned to a key.

Caution The default index property and the Set() method will overwrite a key with multiple values with a single value. In other words, you will lose all values assigned to the key and they will be replaced with the new single value.

To get all the keys in the collection, you use the AllKeys property. This property returns an array, which has been cached for better performance and is automatically refreshed when the collection changes.

array<String^>^ keys = nvCol.AllKeys;

There are two different ways of getting the values using a key: either as an array of strings using the GetValues() method or as a comma-delimited list using the Get() method.

array<String^>^ vals = nvCol.GetValues("Flower"); String ^vals = nvCol.Get("Flower");

It is also possible to manipulate the collection using indexes. To get a key at a specific index, use the GetKey() method.

String ^key = nvCol.GetKey(1);

To get the values at a specific index, you use the default index property, but this time passing a numeric index. Using the default index property this way returns a comma-delimited list of values.

String ^vals = nvCol[3];

You remove a specific key and all its values from the collection by passing the index of the key you want to remove into the Remove() method.

Listing 7-8 shows the NameValueCollection in action.

Listing 7-8. Working with NameValueCollection

#using <system.dll>

using namespace System;

using namespace System::Collections::Specialized;

void main()

{

NameValueCollection^ nvCol = gcnew NameValueCollection();

nvCol->Add(nullptr, "void");

nvCol->Set("Flower", "Rose");

C H A P T E R 7 C O L L E C T I O N S

263

nvCol->Add("Animal", "Dog"); nvCol->Add("Animal", "Cat"); nvCol->Add("Animal", "Cow");

nvCol->Add("Fruit", "Apple"); nvCol->Add("Fruit", "Pear"); nvCol->Add("Fruit", "Peach");

array<String^>^ keys = nvCol->AllKeys;

Console::WriteLine("Key\t\tValue"); for (int i = 0; i < keys->Count; i++)

{

array<String^>^ vals = nvCol->GetValues(keys[i]);

Console::WriteLine("{0}:\t\t{1}", keys[i], vals[0]); for (int j = 1; j < vals->Count; j++)

{

Console::WriteLine("\t\t{0}", vals[j]);

}

}

Console::WriteLine("------ Index Lookups ------");

Console::WriteLine("Key @[1]:\t{0}", nvCol->GetKey(1));

Console::WriteLine("Values @[3]:\t{0}", nvCol[3]);

nvCol->Remove(nullptr);

nvCol["Fruit"] = "Plum";

nvCol->Set("Animal", "Deer"); nvCol->Add("Animal", "Ape");

keys = nvCol->AllKeys;

Console::WriteLine("--------- Updated ---------"); for (int i = 0; i < keys->Count; i++)

{

Console::WriteLine("{0}:\t\t{1}", keys[i], nvCol->Get(keys[i]));

}

Console::WriteLine();

}

Figure 7-9 shows the results of the NameValue.exe program.

264

C H A P T E R 7 C O L L E C T I O N S

Figure 7-9. Results of NameValue.exe

Generic Collections

Originally, I was expecting to write a lot about this set of collections. As I started to work with them, I realized that I’d already covered most of what you need to know earlier in the chapter. The reason for this— the only big difference between generic collections and the standard ones—is the initial code to create the collection and the collection once defined only allows the data type defined in the collection’s declaration (or one inherited from it) as an element of the collection. This differs from the standard collection since standard collections don’t care which managed types you add to the collection.

Most of the collection types within the generic collection set have standard collection equivalents. The one noticeable and, I think, welcome addition is the LinkedList<T>. I’m not sure I know why it does not have a standard set equivalent, especially since it is many C++ programmers’ second choice when it comes to collections (array being the first).

The use of the generic collection set requires either mscorlib.dll:

List<T>

Queue<T>

Stack<T>

Dictionary<K,V>

SortedDictionary<K,V>

or System.dll:

LinkedList<T>

Collection<T>

ReadOnlyCollection<T>

KeyedCollection<K,V>

They all, on the other hand, use the namespace System::Collections::Generic.

One thing that you need to know about generic collections is that none of them support the IsSynchronized or SyncRoot properties and the Synchronized() method (unless you add the functionality yourself). Thus, there is no way to make the default generic collections thread-safe.

C H A P T E R 7 C O L L E C T I O N S

265

Caution The default generic collections cannot be made thread-safe.

List<T>

The List<T> collection is the generic equivalent to the ArrayList. There are some differences, however. List<T> provides most of the functionality of ArrayList; the only notable exception is ArrayList’s ability to fix its length. List<T>, on the other hand, has added some functionality: performing a common operation on all elements using an Action<T> delegate, finding elements based on a Predicate<T> delegate, and determining if all have something in common, again using the Predicate<T> delegate.

I’m not going to cover the features that List<T> and ArrayList have in common; just look them up in the earlier ArrayList section, as they are almost always coded the same way.

The List<T> has three constructors. The first is the default constructor, which has no parameters:

List<T> ^list = gcnew List<T>();

// T is the data type of the list.

You should use this constructor when you have no ballpark idea of how many elements are going to be in the list. If you do know, or have an idea of, how many elements the list contains, then you should use the second constructor, which has a capacity parameter:

List<T> ^list = gcnew List<T>(capacity); // T is the data type of the list.

The reason this constructor is better is because the capacity is already correct (or almost correct) and the collection doesn’t have to perform numerous resizing operations. Remember, though, the caution I mentioned earlier: the collection doubles in size when it needs to perform a resize operation. So if you make the capacity a large number like 32000, and the actual count is 32001, then you’ll get a collection of size 64000 elements. That would be a big waste of memory, though you could perform a TrimToSize() or set the Capacity property directly to get the memory back.

The last constructor takes as a parameter another List<T> from which it makes a copy. The initial capacity is the size of the copied List<T>:

List<T> ^listOrig = gcnew List<T>();

// ... initialize listOrig wth some elements List<T> ^listCopy = gcnew List<T>(listOrig);

Most of List<T>’s new functionally is available because all the data elements within the collection are the same type or inherited from the same type. Therefore, it is safe to perform common operations on each element without having to worry if the element will abort due to type incompatibility. For the List<T> collection, these operations fall onto two delegates: Action<T> and Predicate<T>.

Action<T>

The Action<T> delegate represents the method that performs an action on the specified element of a collection. Its declaration is

public delegate void Action<T>(T obj) sealed;

The parameter obj is the object on which the action is being performed.

When you implement the Action<T> delegate, you can make it either a stand-alone function or a static method within a class. Most likely, you will make it a static member of the class of the type of the obj parameter:

266

C H A P T E R 7 C O L L E C T I O N S

ref class datatype

{

public:

static void ActionDelegate(datatype obj);

};

void datatype::ActionDelegate(datatype obj)

{

// do some operations on obj

}

Predicate<T>

The Predicate<T> delegate represents the method that defines a set of conditions and determines whether the specified object meets those conditions. Its declaration is

public delegate bool Predicate<T>(T obj) sealed;

The parameter obj is the object to which the conditions are being compared.

Just like the Action<T> delegate, when you implement the Predicate<T> delegate, you can make it either a stand-alone function or a static method within a class. Most likely, you will make it a static member of the class of the type of the obj parameter:

ref class datatype

{

public:

static void PredicateDelegate(datatype obj);

};

void datatype::PredicateDelegate(datatype obj)

{

// compare conditions on obj

}

Using Action<T> and Predicate<T>

The Action<T> delegate is used with the List<T>’s ForEach() method. This method allows you to perform specific actions on each element of a List<T> based on the type of List<T>. This differs from the for each statement, because the for each statement performs the same operations on the List<T> no matter what its type. The syntax of the ForEach method is simply

list->ForEach(gcnew Action<datatype>(datatype::ActionDelegate));

Upon completion of this method, every element of the list will have had the ActionDelegate performed upon it.

The Predicate<T> delegate is used by several methods within the List<T> class:

Exists(): Determines if elements match the criteria of the Predicate<T>

Find(): Returns the first element that matches the criteria of the Predicate<T>

FindAll(): Returns all elements that match the criteria of the Predicate<T>

C H A P T E R 7 C O L L E C T I O N S

267

FindIndex() Returns a zero-based index to the first element that matches the criteria of the

Predicate<T>

FindList(): Returns the last element that matches the criteria of the Predicate<T>

FindLastIndex(): Returns a zero-based index to the last element that matches the criteria of the Predicate<T>

TrueForAll(): Returns true if all elements match the criteria of the Predicate<T>

All of these functions have basically the same syntax. Here is FindAll():

List<datatype>^ ret =

list->FindAll(gcnew Predicate<datatype>(datatype::PredicateDelegate));

Some have overloaded methods with additional parameters to limit the number of elements to work on.

Listing 7-9 shows List<T>, Action<T>, and Predicate<T> in action.

Listing 7-9. Working with List<T>, Action<T>, and Predicate<T>

using namespace System;

using namespace System::Collections::Generic;

// -------- StringEx class ---------------------------------------

ref class StringEx

{

public:

String^ Value;

StringEx(String^ in);

virtual String^ ToString() override;

static bool With_e_Predicate(StringEx^ val); static void SurroundInStars(StringEx^ val);

};

StringEx::StringEx(String^ in) : Value(in) {}

String^ StringEx::ToString() { return Value; }

bool StringEx::With_e_Predicate(StringEx^ val)

{

return val->Value->ToUpper()->IndexOf("E") > 0;

}

void StringEx::SurroundInStars(StringEx^ val)

{

val->Value = String::Format("** {0} **", val->Value);

}