Professional ASP.NET 1.1 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Professional ASP.NET 1.1 [Electronic resources] - نسخه متنی

Alex Homeret

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید






Strongly Typed Collections

The fact that most of the collections classes in the .NET Framework class library are designed to work with the

System.Object type makes them very versatile. However, this flexibility can also be viewed as a minor problem, since it means you have to do a lot of casting (assuming you know the type in the first place). For example, assuming a

Hashtable contains a number of items of the type

Product , you have to cast each item you retrieve. Using C#, you would write:


Hashtable products;

Product p;

p = (Product) product["ProductCode"];


Using VB.NET, you would write:


Dim products As Hashtable

Dim p As Product

p = CType(products("ProductCode"), Product)


Casting types like this isn't difficult, but it can make code less readable, and often leads to silly compiler errors when you forget to cast. Strongly typed collections can resolve these problems. Rather than directly using the collection classes such as

Hashtable in your class, you should instead provide a custom type that encapsulates the collection type being used, and also removes the need for casting.

For example, using C# you would write:


ProductCollection products;

Product p;

p = products["ProductCode"];


Using VB.NET, you would write:


Dim products As ProductCollection

Dim p As Product

p = products("ProductCode")


Implementing a strongly typed collection like this is relatively simple, and it allows you to build in additional rules and error handling, saving you from duplicating them throughout the code.

To a strongly typed collection, you have to:



Define the custom type of the item held in the collection.



Create the collection class, and implement the

Add ,

Remove , and

GetEnumerator methods, as well as the

Item property.



Let's look at each of these in turn.


Defining the Custom Type


In the strongly typed example, a

Product type was used. When implementing a collection, you should always have a custom type, and a collection for that custom type in which the collection name is simply the custom type name appended with

Collection; for example,

Product and

ProductCollection , or

Address and

AddressCollection .

Here is a class definition for a

Product type, written using VB.NET:


Public Class Product


' private fields

Private _code As string

Private _description As string

Private _price As Double


' constructor

Public Sub New(initialCode As String, _

initialDescription As String, _

initialPrice As Double)

Code = initialCode

Description = initialDescription

Price = initialPrice

End Sub


Public Property Description As String

Get

Description = _description

End Get

Set

_description = Value

End Set

End Property


Public Property Code As String

Get

Code = _code

End Get

Set

_code = Value

End Set

End Property


Public Property Price As Double

Get

Price = _price

End Get

Set

_price = Value

End Set

End Property


End Class


The

Product type has three public properties:



Code : A unique code assigned to the product.



Description : A description of the product.



Price : The cost of the product.



All of the properties have a get and set accessor, and their implementation simply stores or retrieves the property value in a private field. The field name is the same as the property name, but prefixed with an underscore. The

Product type has a constructor that accepts three parameters, allowing quick initialization. For example:


Dim p As Product

p = New Product("PROASP3", "Professional ASP 3.0", 39.99)


For the purposes of this example, let's define the classes within the ASP.NET page. Typically, you would define these in a separate compiled assembly. That topic was introduced in Chapters 3 and 4, and is covered in more detail in Chapter 17.


Creating the Collection Class


The

ProductCollection class will support two key features:



Unordered enumeration of all the contained products.



Direct access of a product using a product code.



Since the

Hashtable class provides the collection functionality necessary for implementing these features, you can use a

Hashtable internally within your collection class for holding items. Then, you can aggregate the functionality of

Hashtable and expose it, to provide access to your items in a type safe way that doesn't require casting.

Since an internal

Hashtable is being used to hold the

Product items, define a private field called

_products of type

Hashtable within the collection class. A new object instance is assigned to this field in the constructor:


Dim _products as Hashtable

Public Sub New()

_products = New Hashtable()

End Sub


Implementing the Add Method


The

Add method allows a new

Product to be added to the collection:


Public Sub Add( Item as Product )

If Item Is Nothing Then

Throw New ArgumentException("Product cannot be null")

End If

_products.Add(Item.Code, Item)

End Sub


This method throws an

ArgumentException if a

null item parameter is passed. If the parameter is not

null , the

Code property of the passed

Product is used as the key for the

Product in the contained

Hashtable . Depending on your requirements, you could perform additional business logic validation here and throw additional exceptions.

Implementing the Remove Method


The

Remove method removes a

Product from the collection. The implementation of the method simply calls the

Remove method of the

Hashtable :


Public Sub Remove(Item as Product)

_products.Remove(Item.Code)

End Sub


Implementing the Item Property


The

Item property allows a

Product to be retrieved from, or added to the collection by specifying the product code:


Public Default Property Item(Code as String) as Product

Get

Item = CType(_products(Code), Product)

End Get

Set

Add(Value)

End Set

End Property


The implementation of the

Set accessor calls the

Add method in order to add the new product to the internal

Hashtable . The process is implemented in a way so that any business logic in the

Add method (such as the Null check) is neither duplicated nor missed.

Implementing the GetEnumerator Method


To use your collection class with the

for..each statements in VB.NET and C#, your collection class must have a method called

GetEnumerator . Although not strictly necessary, the

IEnumerable interface is also implemented using the VB.NET

Implements keyword. This is good practice and requires very little work:


Public Class ProductCollection

Implements IEnumerable

' ...

' implement an enumerator for the products

Public Function GetEnumerator() As IEnumerator

Implements IEnumerable.GetEnumerator

GetEnumerator = _products.Values.GetEnumerator()

End Function

' ...

End Class


The

GetEnumerator method has to return an enumerator object that implements the

IEnumerator interface. You could implement this interface by creating another class, but since your collection class is using a

Hashtable internally, it makes much more sense to reuse the enumerator object provided by that class when its values are enumerated. The

Hashtable.Values property returns an

ICollection interface and since the

ICollection interface derives from

IEnumerable , you can call

GetEnumerator to create an enumerator object for the collection of values.


Using the Collection Class


With the

Product and

ProductCollection classes created, you can use them just like the other collections in this chapter, but this time with no casting. For example:


' Page-level variable

Dim _products as ProductCollection = New ProductCollection


Sub Page_Load(sender as Object, events As EventArgs)

' Runs when page is loaded


Dim products As New ProductCollection()

Dim p As product


p = New Product("CAR", "A New Car", 19999.99)

products.Add(p)


p = New Product("HOUSE", "A New House", 299999.99)

products.Add(p)


p = New Product("BOOK", "A New Book", 49.99)

products(p.Code) = p


For Each p In products

outResult1.InnerHtml &= p.Code & " - " & p.Description _

& " - $" & p.Price & "<br />"

Next


products.Remove( products("HOUSE") )

For Each p In products

outResult2.InnerHtml &= p.Code & " - " & p.Description _

& " - $" & p.Price & "<br />"

Next


outResult3.InnerHtml = "Description for code CAR is: " _

& products("CAR").Description


End Sub


The complete code for this example is contained in the samples available for download, in both VB.NET and C#. The result of running this page is shown in Figure 15-16:


Figure 15-16:


The DictionaryBase and CollectionBase Classes


The

DictionaryBase and

CollectionBase classes allow you to create a

Hashtable or

ArrayList collection that can validate, and therefore restrict, the types it contains. It's a simple process to create your own collection class by deriving from these classes.

This simple ASP.NET page defines a

MyStringCollection collection class, adds three strings and one integer, and then displays the contents:


'our custom collection

Class MyStringCollection

Inherits CollectionBase

... implementation goes here...

End Class


Dim names As IList


names = New MyStringCollection


names.Add("Richard")

names.Add("Alex")

names.Add("Dave")


Try

names.Add(2002)

Catch e As Exception

outResult1.InnerHtml = "Error: " & e.Message

End Try


For Each name As String in names

outResult2.InnerHtml &= name & "<br />"

Next


The

Collection base class implements the

IList and

ICollection interfaces. All the members of these interfaces are defined explicitly, which is why in the sample code the

names variables have been defined as type

IList .

Each of the collection base classes provides a number of virtual functions that are called when the collection is modified. For example,

OnClear is called when a collection is cleared;

OnInsert is called when an item is added;

OnRemove when an item is deleted, and so on. By overriding one of these methods, you can perform additional checks and throw an exception if an undesired condition arises. For example, in the collection class, you could implement an

OnInsert method that throws an

ArgumentException if anything other than a string is added:

Class MyStringCollection
Inherits CollectionBase

Overrides Protected Sub OnInsert(index as Integer, item as Object)

If Not(TypeOf item Is String) Then

Throw New ArgumentException("My collection only supports strings")

End If

End Sub
End Class


Figure 15-17 shows the results of running this code:


Figure 15-17:

The

DictionaryBase class is used in the same way as the

CollectionBase class and implements the

IDictionary and

ICollection interfaces.

The ReadOnlyCollectionBase Class


The

ReadOnlyCollectionBase class provides functionality for exposing a read-only collection. The class implements the

ICollection and

IEnumerable interface. The items exposed are internally held in a protected

ArrayList variable called

InnerList . To use this class, you have to derive your own class from it, and populate the contents of the

InnerList array.


Disposable Enumerators


When you enumerate a collection, the enumerator objects that implement the

IEnumerator interface may require expensive resources. For example, depending on how underlying items are stored, a custom enumerator could be using a database connection, or be holding temporary files on disk. In these scenarios, it is important that the enumerable object releases resources it holds as soon as possible.

Due to the non-deterministic way the CLR releases object references, any code you write that directly uses an

IEnumerator interface must always check if the enumerator objects that provided the interface support the

IDisposable interface. You must then call the

Dispose method when you've finished with the enumerator. If you do not do this, the resources held by the enumerator may not be released for some time. When you use the

for..each language statement in C# and VB.NET, this is done automatically .

When you use the

IEnumerator interface directly (or any other enumerable type), if you do not know whether an enumerator object supports the

IDisposable interface, always check once you have finished with it. For example, in C#, you might write:


IEnumerator e = c.GetEnumerator();

try

{

while (e.MoveNext())

{

Foo x = e.Current;

// ...

}

}

finally

{

IDisposable d = e as IDisposable;

if (d != null) d.Dispose();

}


If you know that an enumerator object supports

IDisposable , you can call it directly:


IEnumerator e = c.GetEnumerator();

try

{

while (e.MoveNext())

{

Foo x = e.Current;

// ...

}

}

finally

{

((IDisposable)e).Dispose();

}


/ 244