You have some particular data type (and its descendent types) that you wish to store in a collection, and you do not want users of your collection to store any other data types within it.
Create a strongly typed collection by inheriting from the
CollectionBase
abstract
base class. There are two ways to create a strongly typed collection;
the first is to modify the parameters for all the overloaded methods
to accept only a particular type. For example, instead of the
Add
method accepting a generic
Object
data type, you can change it to accept only
one particular data type. A collection base that accepts only objects
of a particular type (Media
) or its descendents
(Magnetic
, Optical
, or
PunchCard
) is shown here (note that the
Media
class and its descendents are defined in
Recipe 3.4):
public class MediaCollection : CollectionBase { public MediaCollection( ) : base( ) { } public Media this[int index] { get { return ((Media)List[index]); } set { List[index] = value; } } public int Add(Media item) { return (List.Add(item)); } public int IndexOf(Media item) { return(List.IndexOf(item)); } public void Insert(int index, Media item) { List.Insert(index, item); } public void Remove(Media item) { List.Remove(item); } public bool Contains(Media item) { return(List.Contains(item)); } }
The next method of writing a strongly typed collection involves the
OnValidate
event. This event is fired immediately
before any action that modifies the data within the collection. The
next strongly typed collection operates the same as the previous
MediaCollection
class, except that it uses an
event to make sure that only a particular type and/or its descendents
are operated on:
public class MediaCollectionEv : CollectionBase { public MediaCollectionEv( ) : base( ) { } public object this[int index] { get { return (List[index]); } set { List[index] = value; } } public int Add(object item) { return (List.Add(item)); } public int IndexOf(object item) { return(List.IndexOf(item)); } public void Insert(int index, object item) { List.Insert(index, item); } public void Remove(object item) { List.Remove(item); } public bool Contains(object item) { return(List.Contains(item)); } protected override void OnValidate(object item) { if (!(item is Media)) { throw new ArgumentException("This collection only accepts " + "the Media type or types that derive from Media"); } } }
Most of the collection types built in to the FCL are generic; that
is, they accept only the most basic type—the
Object
type. Sometimes it is good to have a more
specialized collection type (usually referred to as
strongly typed collections) that can contain only objects of one
particular type. Of course, this collection would also be able to
contain objects of types descending from this one particular type.
There are several benefits to writing a strongly typed collection,
such as reducing the number of potential errors that can be coded
into your application. If you are only expecting a particular type to
be contained within a collection, and a piece of code inadvertently
adds objects not of this type, your code might fail when attempting
to operate on this unexpected type. If the first of the two strongly
typed collections were used, the compiler would catch this mistake
earlier in the development phase. Note that the
OnValidate
event will work only at runtime.
Another useful side effect of using the first of the two strongly
typed collections is that you do not have to cast the object being
returned from the collection to its correct type before using it. A
strongly typed collection automatically returns the type you expect,
as opposed to an Object
type, which must then be
cast to the expected type.
A benefit of either strongly typed collection is that you can add
specialized code to your collection to more easily allow you to
operate on the objects contained in your collection. For example, if
you wrote a strongly typed collection to contain only
Invoice
type objects, you could add methods to
this collection to do the following:
Retrieve only those invoices that match a specific criteria, such as being overdue.
Reject attempts to add invoices to this collection that do not meet a criterion, such as a minimum amount.
Prevent invoice objects from being removed by throwing a
NotImplementedException
when a Remove
method is called and overloading the RemoveAt
method to do the same, so that invoices cannot be removed.
Now that you have a reason for building a strongly typed collection, you have three choices for doing so:
Inherit from the CollectionBase
abstract base
class and implement the members so that they operate on a specific
type, other than Object
, as in the
MediaCollection
class defined in the Solution
section.
Inherit from CollectionBase
and override the
OnValidate
event, as with the
MediaCollectionEv
class defined in the Solution
section.
Build your own from scratch (this technique is not covered in this recipe since the previous two ways are much easier to implement).
Many developers opt for the first technique, which involves adding
methods to the collection, such as Add
,
Remove
, IndexOf
, and others
that operate on a specific type. This technique best aids the
developer for two important reasons. First, the developer can examine
the exact type that this collection is expecting by using the
Intellisense features of the IDE. Second, the developer is alerted at
compile time when the collection is not being used as it should, via
compile-time errors.
The second technique is very similar to the first technique, but
instead of writing strongly typed methods such as
Add
, Remove
, and so on, these
methods are written to accept and return the
Object
type. Instead of preventing other data
types from being contained in this collection, an event handler,
called OnValidate
, is added to validate the object
being added to, removed from, and so on, with regard to the
collection. If the object is of the correct type, the event handler
does nothing, allowing the collection to perform the specified
action. If the object is not of the correct type, an exception is
thrown, preventing the collection from performing the specified
action.
Note that when using weakly typed methods with the
OnValidate
event handler, the compiler will not be
able to validate any use of the strongly typed collection. However,
this event handler is useful when you want to consolidate all
validation routines for your collection. In fact, there is no reason
why the first and second techniques cannot be combined (i.e.,
strongly typed methods along with the OnValidate
event handler for further validations).
See Recipe 3.4; see the “CollectionBase Class” and “Creating and Manipulating Collections” topics in the MSDN documentation.