The following Visual Basic.NET code shows how we can copy the contents of the ArrayList into a string array at the beginning using the CopyTo method: Dim Animals As ArrayList = new Array
Trang 2The ICollection Interface
The ArrayList class supports three collection interfaces: IList, IEnumerable, and ICollection We've covered the first two, now we'll turn our attention to ICollection The ICollection interface defines methods and properties that allow us to:
Determine how many items are in a collection (using the Count property)
Copy the contents of a collection into a specified array at a given offset (using the CopyTo method)
Determine if the collection is synchronized and therefore thread-safe
Determine the synchronization root object for the collection (using the SyncRoot property) The
synchronization root object is the object that is locked and unlocked as collection operations are performed on a synchronized collection
The ICollection interface derives from the IEnumerable interface so it inherits the GetEnumerator method
The ICollection.Count Property
The following code shows how we can use the ICollection.Count property to display the number of items in our movieList ArrayList:
<p>There are <%=movieList.Count%> in the list.</p>
For those types in the class library, the implementation of the Count property returns a cached field It doesn't cause the size of a collection to be recalculated, so it isn't expensive to call, which means that we don't need to worry about caching the Count property in an effort to get efficiency
The ICollection.CopyTo Method
The ICollection.CopyTo method allows the contents of a collection to be inserted into an array at a specified offset
If the array to which the contents are copied does not have sufficient capacity for the insertion an ArgumentExceptionwill be thrown The following Visual Basic.NET code shows how we can copy the contents of the ArrayList into a string array (at the beginning) using the CopyTo method:
Dim Animals As ArrayList = new ArrayList()
Dim ArrayOfAnimals() As string
Trang 3Dim Animal As String
For Each Animal in ArrayOfAnimals
Response.Write("<p>" & Animal)
Next
Using C# we would write:
ArrayList Animals = new ArrayList();
Trang 4As the ArrayList class does not expose its internal array, we have to use either its AddRange or InsertRange method when we're copying data from an array into an ArrayList The AddRange method accepts an ICollection interface and adds all items within the collection to the end of the array In the following example we copy the contents of two arrays into an ArrayList The code would result in the names being listed in this order: Tom, Mark, Paul, Jon Using C# we would write:
string[] Friends = {"Tom","Mark"};
string[] CoWorkers = {"Paul","Jon"};
ArrayList MergedList = new ArrayList();
Trang 5The InsertRange method accepts an ICollection interface as its second parameter, but expects the first parameter
to be the index at which the new items are to be copied to To make the names Paul and Jon appear at the front of the list, we could insert the second array using InsertRange with an index value of zero:
string[] Friends = {"Tom","Mark"};
string[] CoWorkers = {"Paul","Jon"};
ArrayList MergedList = new ArrayList();
This code would result in the names being listed in this order: Paul, Jon, Tom, Mark
The ICollection.IsSynchronized Property
ASP.NET enables web applications to share objects by using the Application and Cache intrinsic objects If an object reference is shared this way, there is potential for multiple pages to be working simultaneously with the same object instance If this happens, any changes to the object must be synchronized If two pages try to modify an object at the same time without synchronization, there is a high probability that the object's state will become corrupted Objects that can safely be manipulated simultaneously are thread-safe A thread-safe object takes on the responsibility of guarding itself from concurrent access, providing any necessary synchronization
The ICollection.IsSynchronized property can be used to determine if an object is thread-safe For performance reasons, most objects (including ArrayList) are not thread-safe by default This means we should not share types like ArrayList in a web application without performing some form of synchronization In classic ASP we could use the Application.Lock and Application.Unlock methods for synchronization and while they are available in ASP.NET, they're not suitable for this particular task They provide a coarse-grained lock (which is application-wide) that simply isn't
Trang 6suitable for scalable applications
To safely share a collection object such as an ArrayList between multiple web pages, we need to use a synchronization helper object Such a helper object provides the same public interface as a non-thread-safe type, but adds a
synchronization wrapper around the methods and properties that would otherwise not be thread-safe Since this is such
a common requirement, the collection types (and many other framework classes) follow a common pattern
Types that need to work in a thread-safe way provide a public, static, method called Synchronized that takes a non-thread-safe object reference and creates and returns a thread-safe wrapper that can be used to synchronize calls Both objects manipulate the same underlying state, so changes made by either the thread-safe or non-thread-safe object will be seen by any other code that holds a reference to either object
The following (Visual Basic.NET) code shows how a non-thread-safe ArrayList adds an item before creating a thread-safe wrapper:
Dim NotThreadSafeArrayList As ArrayList
NotThreadSafeArrayList = New ArrayList()
Trang 7The ICollection.SyncRoot Property
When writing thread-safe code, it's common to have a handle or object that's used by all the possible code paths in different threads (web pages) in order to synchronize access to the shared state The CLR automatically allows any NET type instance to be a synchronization root (SyncRoot) that can be locked and unlocked to ensure that one or more code statements are only ever executed by a single thread at a time
In Visual Basic.NET we can use an object reference in conjunction with the SyncLock statement to make code thread-safe:
Sub DoSomethingWithAList( list as ArrayList )
SyncLock list
list.Add("abc")
End SyncLock
Trang 8End Sub
In C# we can achieve the same result using the lock statement:
void DoSomethingWithAList( ArrayList list )
Under the hood, both C# and Visual Basic.NET use the System.Threading.Monitor object to perform their
synchronization Both the C# and Visual Basic.NET compilers add exception handling around the statements being executed, to ensure that any exceptions that are not handled in the code do not result in synchronization locks not being released
Using SyncLock (or lock) we can achieve a fine-grained level of locking inside ASP.NET pages Rather than having one global lock, which is effectively what Application.Lock and Application.Unlock provide, we can have many smaller locks, which reduces lock contention The disadvantage to this approach is that it requires that page developers really understand multi-threading Since the writing of multi-threaded applications is not simple (and something ASP.NET does its best to protect us from), using the Synchronized method to automatically make a type thread-safe is often the preferred approach to take
When acquiring or releasing a SyncLock we need to know what SyncRoot object to use This is the function of the ICollection.SyncRoot property Since we cannot assume any implementation knowledge about the type providing
an interface, we cannot make any assumptions about which SyncRoot object to use either Consider this Visual Basic.NET code:
Dim NotThreadSafeArrayList As ArrayList
Trang 9NotThreadSafeArrayList = New ArrayList()
NotThreadSafeArrayList.Add("Hello")
Dim ThreadSafeArrayList1 As ArrayList
Dim ThreadSafeArrayList2 As ArrayList
Dim ThreadSafeArrayList3 As ArrayList
If we wanted to synchronize access to the array in a web page so that we could perform an atomic operation, such as adding the contents of the array to a database and clearing it, we'd need to lock out all other threads from using the ArrayList If we only had a reference to an ArrayList object or an ICollection interface, and we didn't know whether or not that type was thread-safe, how could we achieve this? If we had a thread-safe wrapper, and wrote our
Trang 10code like this, it would fail miserably:
SyncLock ThreadSafeArrayList1.SyncRoot
' Copy list to db and clear list (as an example)
End SyncLock
Working with Dictionary Objects
With ASP 3 the state can be managed using the Session or Application intrinsic objects These classes enable us to store and retrieve a variant value using a string key With ASP.NET the same functionality is provided by the intrinsic objects, but the value type stored is now of type System.Object rather than variant Since all types in NET derive from System.Object, any built-in or custom type can be held in session or application state
The following code shows a simple example of storing and retrieving state using the Session intrinsic object Using Visual Basic.NET we would write:
Session("value1") = "Wrox"
Session("value2") = "Press"
Dim value As String
value = CType(Session("value1"), string)
Trang 11value = CType(Session("value2"), string)
Using C# we would write:
Session["value1"] = "Wrox";
Session["value2"] = "Press";
string value;
value = (string) Session["value1"];
value = (string) Session["value2"];
Here two values are being set using the keys value1 and value2 The values associated with these keys are then retrieved
The Hashtable Class
The Hashtable class represents a dictionary of associated keys and values, implemented as a hash table A hash table
is a proven way of efficiently storing and retrieving values using the hash of a key value
Internally, the ASP intrinsic objects such as Session use the System.Collections.Hashtable class to implement key-based lookup functionality This class is very similar to the scripting run-time Dictionary object used in classic ASP pages, and provides all those methods expected from a dictionary type object
Using the Hashtable class is straightforward We can create an instance of the Hashtable class and use a text indexer
to set and retrieve associated keys and values For example, using C# we would write:
Hashtable ourSession;
ourSession = new Hashtable();
ourSession["value1"] = "Wrox";
ourSession["value2"] = "Press";
Trang 12string value;
value = (string) ourSession["value1"];
value = (string) ourSession["value2"];
We can determine if a key is contained within the hash table using the ContainsKey method Using Visual Basic.NET we would write:
If ourSession.ContainsKey("value1") = True Then
Although the Hashtable allows any object type to be used as a key, only those types that override
System.Object.GetHashCode and System.Object.Equals should actually be used All of the primitive types
in NET, such as integer and string, override these methods and are safe to use as keys
A hash table depends on hash codes to uniquely identify keys Hash codes are numbers that, where possible, uniquely identify a single object instance of a specific type A perfect hash code algorithm will always return a hash code that is unique to a single instance of a specific type A good candidate for a hash code is something along the lines of the identity field for a row within a database The Hashtable class uses hash codes to provide efficient and fast lookups When we create custom types we also have to implement a good hash code algorithm
As any type can be used with most of the collections in the NET Framework class library, the System.Object class has
a method called GetHashCode that returns a hash code for any object instance The system-provided implementation of this method returns a hash code that uniquely identifies an object instance, but it's not specific to a given type The returned value is simply an index held internally by the CLR to identify an object This means that if the system-provided implementation of GetHashCode is not overridden by a type, then by default, if two hash code values are the same then
Trang 13it's the same object instance, as demonstrated in this C# code:
Hash codes enable classes like Hashtable to efficiently store and retrieve values When an item is added to a
Hashtable, the hash code of its associated key is used to determine a slot in which an item can be held If a key with the same hash code doesn't already exist at the slot located using the hash code, the value can be saved If the slot is already full, System.Object.Equals is called to determine if the key already in the slot is in fact equal to the one used to locate the slot If both keys are equal, the existing value is overwritten If the keys are different, a new key and value is added
to the hash table in a different slot
For these reasons, any type used as a key within a hash table must:
Implement an efficient hash code algorithm that, where possible, uniquely identifies an object of a specific type
We must therefore override System.Object.GetHashCode
Override the System.Object.Equals method and provide an efficient comparison of two object instances Typically, we would implement this by comparing the hash codes (if we can be sure that the hash code is always unique), or key fields that are held within a type as fields
We can enumerate all of the keys and values in a hash table using the Keys or Values properties Both of these properties are defined as the type ICollection, so they can be accessed in numerous ways As a demonstration, the following code uses the C# foreach statement to enumerate the keys This code will actually result in a call to table.Keys.GetEnumerator (recall that ICollection inherits this from IEnumerable) This method will return an enumerator object that implements IEnumerator, which we can then use to walk through each key value:
Trang 14<%
Hashtable table = new Hashtable();
table["name"] = "Richard";
table["age"] = "Old enough";
foreach (string key in table.Keys)
Dim table As Hashtable = New Hashtable()
Dim value As string
table("name") = "Richard"
table("age") = "Old enough"
For Each value In table.Values
Response.Write("<br />" & value)
Next
Trang 15%>
The Hashtable class implements the interface IEnumerable, so we can enumerate all its contained keys and items The enumerator object returned by the Hashtable's implementation of IEnumerable.GetEnumerator exposes contained items using the value type System.Collections.DictionaryEntry This value type has properties that allow us to access the associated Key and Value properties To list all of the keys and current values held in a Hashtable
we could write the following C# code:
foreach (DictionaryEntry entry in table)
The following C# code shows how we could use the IDictionaryEnumerator interface:
Trang 16When the type implements the IEnumerable interface, it still has to implement a version of the GetEnumeratormethod, which returns an IEnumerator interface The following C# code shows how we would implement an
enumerable type that supports both the standard IEnumerable.GetEnumerator method using an explicit interface method definition, and a public GetEnumerator method that reduces the need for casting:
public class MyCollection : IEnumerable
Comparing Hashtable with the Intrinsic ASP.NET Objects
There are a number of differences between using the Hashtable class and the ASP.NET intrinsic objects:
The key values for a Hashtable are not restricted to strings Any type can be used, so long as the type used as
a key overrides the System.Object.Equals and System.Object.GetHashCode methods
String keys are case-sensitive whereas those of the ASP.NET intrinsic objects are not
When enumerating over an ASP.NET intrinsic object, we are enumerating over the keys associated with each contained item value The Hashtable returns both the key and value using the DictionaryEntry class
Trang 17 When enumerating over an ASP.NET intrinsic object the Current property returns a System.String object (the key value) and not a System.Collections.DictionaryEntry entry
The IDictionary Interface
The IDictionary interface defines methods and properties that allow us to work with an unordered collection of keys and their associated values Keys and values are both defined in this interface as type System.Object, which means any type can be used as a key, and any type can be used as a value The IDictionary interface defines methods and properties that allow us to:
Add items to the collection (by passing in the key and value to the Add method)
Delete items from the collection (by passing the key of the value to delete to the Remove method)
Determine if a specified key exits and is associated with an item (using the Contains method)
Empty the collection (by calling the Clear method)
Enumerate all of the keys in the collection (using the Keys property, which is defined as an ICollection)
Enumerate all of the values in the collection (using the Values property, which is defined as an ICollection)
Enumerate key-value pairs (by using the GetEnumerator method, which returns an
IDictionaryEnumerator interface that we'll discuss shortly)
Determine if the collection is read-only (using the IsReadOnly property)
Determine if the collection is of a fixed size (using the IsFixedSize property)
The IDictionary interface derives from the ICollection interface so it inherits all of its methods and properties, as well as those of the basic interface IEnumerable
Case Sensitivity of Keys
With the ASP.NET intrinsic objects, key values are case insensitive This means the following C# code, which uses the intrinsic Session object, will happily set and retrieve the value correctly, even though the case in the key is different:
Session["key"] = "value";
Response.Write("<p>value is " + Session["KEY"]);
The following C# code shows the hash codes for two objects that contain the same string value, but with differing case:
Trang 18Since the NET Framework is case-sensitive by default, the hash codes for these strings are unique:
If we want two strings that differ only by case to be considered the same, we have to implement a hash code provider class that determines hash codes for other types, and can therefore use a different algorithm to create a hash code, one which
is not based on case-sensitive letters Since it's common to have case-insensitive keys, the NET Framework has a built-in hash code provider that is case-insensitive:
<%@Page Language="C#"%>
<%
Trang 19string name1, name2;
System.Collections.CaseInsensitiveComparer:
Hashtable ourSession;
ourSession = new Hashtable(CaseInsensitiveHashCodeProvider.Default,
Trang 20Both CaseInsensitiveHashCodeProvider and CaseInsensitiveComparer have a static property called Default that returns an instance of the class that is shared and always available
Creating a case-insensitive hash table is a common requirement, and so a helper class,
System.Collections.Specialized.CollectionsUtil, is provided to assist For example:
Hashtable names;
names = CollectionsUtil.CreateCaseInsensitiveHashtable();
The System.Collections.Specialized namespace is imported by default into a ASP.NET page so we do not need to use fully qualified names or an import directive
The Stack Class
The Stack class provides a Last-in First-Out (LIFO) collection of System.Object types The last item pushed onto the stack is always the first item retrieved from the stack The Stack class can be extremely useful when we need to perform recursive operations in which we often need to save and restore values in order (such as the context within an XSLT processor) but in which the number of values saved and restored is not necessarily known in advance A Stack class can also be useful if we simply need to save the values of some variables while performing a temporary calculation, after which
we need to recall their original values