logo
  • Jobs
  • About Me
  • Contact
  • Home
« MovableType 3.11
What’s in your computer? »

Implementing CollectionBase

Posted September 5th, 2004 by Matt Berther

Update: For you code-genners out there, I’ve created a CodeSmith template that I use to generate these implementations. Download it here. Also, Chris Lane has created a VB.NET version of the CodeSmith template, available here. Enjoy!

Update: Leif Wickland has updated the C# template to get rid of three compiler warnings. Make sure to get the new version. Thanks, Leif!

CollectionBase is probably one of my favorite classes in the .NET 1.1 framework. I tend to do a lot of work that involves creating collections of objects and I really enjoy having strongly typed access to my collections.

Implementing CollectionBase is very easy. Usually, you’ll just add an Add and Remove method. An indexer, IndexOf and a Contains method, if you like. Simple.

There are a few things to keep in mind when implementing CollectionBase, however.

It is important to override the OnValidate method. This method should throw an Exception if the passed object does not belong in the current collection. I think this is probably the one thing I see neglected most often. The reason why this is important is that CollectionBase has an explicit IList implementation. This means that a user can cast your CollectionBase derived object to an IList and call any of the methods on the IList. Suddenly, your user is putting incorrect objects into your collection.

For example:

public class PersonCollection : CollectionBase
{
    protected override void OnValidate(object value)
    {
        base.OnValidate(value);
        if (!(value is Person))
        {
            throw new ArgumentException("Collection only supports Person objects.");
        }
    }
}

Also important is to mark your new collection with the Serializable attribute. The Serializable attribute is applied to the CollectionBase class, however, this attribute is not inherited. It’s easier to do this now rather than later when you find a serialization bug that has manifested itself in some strange way. Trust me, just add the attribute.

A lot of times, you’ll want to return a ReadOnly collection from one of your properties. For example, say you’re returning a list of States to populate a state list. You will not want to allow the user to modify this collection once it has been generated by your object. You can accomplish this and still use CollectionBase.

Take a look at this:

[Serializable]
public class PersonCollection : CollectionBase
{
    public PersonCollection()
    {
    }
 
    public PersonCollection(PersonCollection coll)
    {
        this.InnerList.AddRange(coll);
    }
 
    public Person this[int index]
    {
        get { return (Person)List[index]; }
        set { List[index] = value; }
    }
 
    public virtual void Add(Person person)
    {
        List.Add(person);
    }
 
    public virtual void Remove(Person person)
    {
        List.Remove(person);
    }
 
    public bool Contains(Person person)
    {
        return List.Contains(person);
    }
 
    public int IndexOf(Person person)
    {
        return List.IndexOf(person);
    }
 
    public static PersonCollection ReadOnly(PersonCollection coll)
    {
        return new PersonCollection.ReadOnlyPersonCollection(coll);
    }
 
    protected override void OnValidate(object value)
    {
        base.OnValidate(value);
        if (!(value is Person))
        {
            throw new ArgumentException("Collection only supports Person objects.");
        }
    }
 
    #region ReadOnlyPersonCollection
    private sealed class ReadOnlyPersonCollection : PersonCollection
    {
        private const string ERROR_STRING = "Collection is read-only.";
 
        internal ReadOnlyPersonCollection(PersonCollection coll) : base(coll)
        {
        }
 
        public override void Add(Person person)
        {
            throw new NotSupportedException(ERROR_STRING);
        }
 
        public override void Remove(Person person)
        {
            throw new NotSupportedException(ERROR_STRING);
        }
 
        protected override void OnClear()
        {
            throw new NotSupportedException(ERROR_STRING);
        }
 
        protected override void OnInsert(int index, object value)
        {
            throw new NotSupportedException(ERROR_STRING);
        }
 
        protected override void OnRemove(int index, object value)
        {
            throw new NotSupportedException(ERROR_STRING);
        }
 
        protected override void OnSet(int index, object oldValue, object newValue)
        {
            throw new NotSupportedException(ERROR_STRING);
        }
    }
    #endregion
}

We added a static method called ReadOnly, which takes a PersonCollection and returns a PersonCollection. Inside of our PersonCollection object, we have a nested class called ReadOnlyPersonCollection, which derives from PersonCollection and provides the implementation that will make the collection read-only. Since ReadOnlyPersonCollection derives from PersonCollection, we have no problem returning it from our static ReadOnly method.

We see here that our ReadOnlyPersonCollection is also overriding a few additional methods. CollectionBase.Clear is not marked as virtual, so thankfully, Microsoft has provided us a way to stop the Clear from occurring. Prior to the list.Clear() call, OnClear() is called. Similar functionality is available for insert, update and delete. These methods are all overridden, since a read-only collection should not be able to be modified in any way.

Of course, .NET 2.0, with the introduction of generics, will render all of this information invalid. All of my CollectionBase implementations will be replaced with IList<Type>. :=)

This entry was posted on Sunday, September 5th, 2004 at 5:05 pm and is filed under Uncategorized. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

Christian Romney
September 5th, 2004

Matt, good post. For completeness, you should probably implement int IndexOf(Person person); as well. In fact FxCop 1.30 will complain if you don’t.

Matt Berther
September 5th, 2004

Christian, thanks for the tip. You’re completely right. I’ve added the implementation of IndexOf to the sample code.

ISerializable
September 7th, 2004

Implementing CollectionBase the right way

Hello??? .... is this thing on??
September 8th, 2004

Collection Base Implementation Tips

Angelos Petropoulos
September 10th, 2004

Well, what happens if you give a ReadOnly copy to a client and you modify the original collection?

PersonCollection collection = new PersonCollection();
collection.Add(new Person());
PersonCollection readOnlyCollection = PersonCollection.ReadOnly(collection);
collection.Add(new SomeClass());

Now collection has two items and readOnlyCollection has only one.

The ReadOnly copy will not be in-sync. What you really want is for the ReadOnly static class to not be a true instance of a collection but rather a simply wrapper that forwards any calls to the actual collection.

PersonCollection innerCollection;
internal ReadOnlyPersonCollection(PersonCollection collection)
{
this.innerCollection = collection;
}

example method:

public override bool Contains(Person person)
{
return this.innerCollection.Contains(person);
}

This works nicely until you realise that CollectionBase implements a Count property which you cannot override. Great.

Matt Berther
September 10th, 2004

Angleos: You have a very valid point. Regarding CollectionBase.Count, I may be way off, but wouldnt

public new int Count { get return innerCollection.Count; }

do the trick?

Angelos Petropoulos
September 10th, 2004

Unfortunately that would not work. The new keyword means that the method/property is available only when the object is casted to the type that the method/property is specified in.

Here is an example with the new Count property implemented.

PersonCollection collection = new PersonCollection();
collection.Add(new Person());
collection.Add(new Person());

ReadOnlyPersonCollection readOnlyCollection = PersonCollection.ReadOnly(collection);

Console.WriteLine(readOnlyCollection.Count.ToString());

PersonCollection castedReadOnlyCollection = (PersonCollection) readOnlyCollection;

Console.WriteLine(castedReadOnlyCollection.Count.ToString())

The output would be:

2
0

Matt Berther
September 10th, 2004

I’ll concede the implementation of ReadOnly collection. I’ll also say that I do like the way things are done in Java, regarding inheritance. You can override *anything* unless it is specifically declared final. I’m not sure why this is backwards in C#.

The other points are still valid when implementing CollectionBase…

Angelos Petropoulos
September 10th, 2004

I haven’t given it great thought, but I feel the same way regarding overriding anything unless explicitly not being allowed to. It is more logical in the sense that it is more likely to identify a method as one an inheriting class should not be able to change, rather than one it will need to change.

I really cannot understand why they created a base (as the name demonstrates) class that has a property that cannot be overridden.

For my custom collection needs I resorted in custom classes that implement all the required interfaces (ICollection, IList, etc.) from the ground up, using arrays internally. It’s such a bad feeling having to copy paste and doing search and replace every time I need a new strongly typed collection …

Matt Berther
September 10th, 2004

If you havent looked at it, this is where CodeSmith comes in really handy.

Code generation of things like this has saved me a significant amount of time lately.

Christian Romney
September 17th, 2004

Can’t wait till we can just new ReadOnlyCollection<Person>();

Matt Berther
September 17th, 2004

As I mentioned in the last paragraph of my article, all of this code will be replaced with one line in .NET 2.0.

IList<Person> coll = new List<Person>();

I love how technology advances… :=)

Chris Lane
September 28th, 2004

I am a newbie so I was just wondering,
Why not just use the ReadOnlyCollectionBase Class?

Matt Berther
September 28th, 2004

Chris: Ultimately, I believe that using ReadOnlyCollectionBase suffers from the same limitations. Once you modify the original copy, the ReadOnly copy is invalid.

Chris Lane
September 29th, 2004

I am having problems trying to convert this example to VB.NET. VS is complaining about the multipile inherits with in a class since the readonly class is within the other class and the regions tags are messed up. Does anybody have a VB.NET version of this template or the result of the template?
Here is what I have so far.
Imports System
Imports System.Collections
Imports Customer

Namespace BusinessObjects.MyClass

_
Public Class CustomerCollection
Inherits CollectionBase

Public Sub New()

End Sub

Public Sub New(ByVal col1 As CustomerCollection)

Me.InnerList.AddRange(col1)

End Sub

Default Public Property aCustomer(ByVal index As Integer) As Customer

Get

Return CType(List(index), Customer)

End Get

Set(ByVal Value As Customer)

List(index) = Value

End Set

End Property

Public Overridable Function Add(ByVal value As Customer) As Integer

Return List.Add(value)

End Function

Public Overridable Sub Insert(ByVal index As Integer, ByVal value As Customer)

List.Insert(index, value)

End Sub

Public Overridable Sub Remove(ByVal value As Customer)

List.Remove(value)

End Sub

Public Function Contains(ByVal value As Customer) As Boolean

Return List.Contains(value)

End Function

Public Function IndexOf(ByVal value As Customer) As Integer

Return List.IndexOf(value)

End Function

Public Sub CopyTo(ByVal value() As Customer, ByVal index As Integer)

List.CopyTo(value, index)

End Sub

Public Static Function ReadOnlyCustomerCollection(ByVal col1 As CustomerCollection)

Return New CustomerCollection.ReadOnlyCustomerCollection(coll)

End Function

Protected Overrides Sub OnValidate(ByVal value As Object)

MyBase.OnValidate(value)

If Not TypeOf value Is Customer Then

Throw New ArgumentException(”Collection only supports Customer objects.”)

End If

End Sub

Public Enum AccessibilityEnum
[Public]

[Protected]

[Internal]

[ProtectedInternal]

[Private]
End Enum

Public Function GetAccessModifier(ByVal accessibility As AccessibilityEnum) As String

Select Case accessibility

Case AccessibilityEnum.Public

Return “public”

Case AccessibilityEnum.Protected

Return “protected”

Case AccessibilityEnum.Internal

Return “internal”

Case AccessibilityEnum.ProtectedInternal

Return “protected internal”

Case AccessibilityEnum.Private

Return “private”

Case Else

Return “public”

End Select

End Function
#Region “ReadOnlyClass”

Private NonInheritable Class ReadOnlyCustomerCollection
Inherits CustomerCollection

Private Const ERROR_STRING As String = “Collection is read-only.”

Friend ReadOnly Sub New(ByVal value As Customer)
MyBase.New(coll)
End Sub

Public Overrides Function Add(ByVal value As Customer) As Integer

Throw New NotSupportedException(ERROR_STRING)

End Function

Public Overrides Sub Remove(ByVal value As Customer)

Throw New NotSupportedException(ERROR_STRING)

End Sub

Protected Overrides Sub OnClear()

Throw New NotSupportedException(ERROR_STRING)

End Sub

Protected Overrides Sub OnInsert(ByVal index As Integer, ByVal value As Object)

Throw New NotSupportedException(ERROR_STRING)

End Sub

Protected Overrides Sub OnRemove(ByVal index As Integer, ByVal value As Object)

Throw New NotSupportedException(ERROR_STRING)

End Sub

Protected Overrides Sub OnSet(ByVal index As Integer, ByVal oldValue As Object, ByVal NewValue As Object)

Throw New NotSupportedException(ERROR_STRING)

End Sub

End Class

#end Region

End Class

End Namespace

Matt Berther
September 29th, 2004

Chris,

Im not sure what you’re trying to do here. It looks like you’re trying to combine the codesmith file with a .vb file.

Which one are you trying to get to work?

Chris Lane
September 30th, 2004

Matt,

Doh, Sorry about that, The code I posted is an attempt at trying to convert the output of the C# file that the template creates to VB.NET, then I was going to convert the template to VB Style.

Thank You,

Chris

Matt Berther
September 30th, 2004

And you’re using CodeSmith to transform the template? If so, send me an email with your file as an attachment, and Ill be glad to take a look at it.

Matej Rizman
March 23rd, 2005

Chris,

I found error in VB.NET version of template. Instead of

Default Public Property aCustomer(ByVal index As Integer) As Customer

it should generate

Default Public Property Item(ByVal index As Integer) As Customer

If this property is not named “Item”, collection editor in PropertyGrid does not recognize generated collection as a strongly-typed collection.

Matej Rizman

arun
May 5th, 2005

Sir
I created collection property using collection base class.I want to limit the object added (say 10) to the collection.If it is greater than 10 i want to throw an error dialogbox.How to control the no. of objects added to a collection in design time.

Matt Berther
May 5th, 2005

It seems to me that you would need to implement the Add method to perform a check if the inner list has a count >= 10 before adding.

public void Add(MyType obj)
{
if (InnerList.Count >= 10)
{
throw new ApplicationException(”This collection can only hold 10 objects.”);
}
Innerlist.Add(obj);
}

Sudhakar
May 5th, 2005

i want to create windows installer project in which i want to restrict the installation based on the ‘Product serial number’ entered in the ‘Customer information’ user interface custom dialog box. i also added the custom action and able to validate the product serial number entered. but my problem is unable to terminate the installation if user enters the wrong serial number.

can anybody share your experience on this ?

Thanks in advance.
Sudhakar-D
chudhakar@yahoo.com

heracles maroudas
April 17th, 2006

heres a nice update for this article: a C# code snippet for vs 2005. just place the xml into a .snippet file and add to vs from the tools menus Code Snippet Manager.

once imported type the short name classCollectionBase and press tab from within the ide…

heracles maroudas
April 17th, 2006

Strongly Typed CollectionBase Class
Eric Maroudas
Creates a Strongly Typed Collection Base Class Implementation.
classCollectionBase

Expansion

ClassName
Replace with your collection base class name.
className

TypeName
Replace with your custom type class.
yourType

ItemName
Replace with an item name of your choice
item

{
#region Implemantation

public int Add($TypeName$ $ItemName$)
{
return ((IList)this).Add((object)$ItemName$);
}

public void Remove($TypeName$ $ItemName$)
{
((IList)this).Remove((object)$ItemName$);
}

public void Insert(int index, $TypeName$ $ItemName$)
{
((IList)this).Insert(index, (object)$ItemName$);
}

public bool Contains($TypeName$ $ItemName$)
{
return ((IList)this).Contains((object)$ItemName$);
}

public int IndexOf($TypeName$ $ItemName$)
{
return ((IList)this).IndexOf((object)$ItemName$);
}

public void CopyTo($TypeName$[] $ItemName$Array, int index)
{
((ICollection)this).CopyTo($ItemName$Array, index);
}

public $TypeName$ this[int index]
{
get { return ($TypeName$)((IList)this)[index]; }
set { ((IList)this)[index] = value; }
}

#endregion Implemantation

#region IEnumerable Members

public new IEnumerator GetEnumerator()
{
foreach ($TypeName$ $ItemName$ in InnerList)
{
yield return $ItemName$;
}
}

#endregion IEnumerable Members

#region Overrides

protected override void OnValidate(object value)
{
base.OnValidate(value);
if (!(value is $TypeName$))
{
throw new ArgumentException(”This type only supports objects of this type.”);
}
}

#endregion Overrides
}$end$]]>

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
-->

Social
  • mattberther on twitter
Syndication
Archives
  • August 2008
  • June 2008
  • May 2008
  • April 2008
  • March 2008
  • February 2008
  • January 2008
  • December 2007
  • November 2007
  • October 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007
  • February 2007
  • January 2007
  • December 2006
  • November 2006
  • October 2006
  • September 2006
  • August 2006
  • July 2006
  • June 2006
  • May 2006
  • April 2006
  • March 2006
  • February 2006
  • January 2006
  • December 2005
  • November 2005
  • October 2005
  • September 2005
  • August 2005
  • July 2005
  • June 2005
  • May 2005
  • April 2005
  • March 2005
  • February 2005
  • January 2005
  • December 2004
  • November 2004
  • October 2004
  • September 2004
  • August 2004
  • July 2004
  • June 2004
  • May 2004
  • April 2004
  • March 2004
  • February 2004
  • January 2004
  • December 2003
  • November 2003
  • October 2003
  • September 2003
  • August 2003
  • July 2003
  • June 2003
  • May 2003
  • April 2003
  • March 2003
Jobs
mattberther.com © 2003 - 2008